# Meine API-Schlüssel erhalten
Source: https://api-docs.jarvi.tech/de/api-keys
Ihre API-Schlüssel ermöglichen Ihnen den Zugriff auf die API
Gehen Sie zu Ihren Einstellungen und dann zu API. Klicken Sie auf die Schaltfläche, um API-Schlüssel zu generieren, wie unten gezeigt.
Sie erhalten dann 2 Schlüssel:
* Einen **öffentlichen Schlüssel**, der nur das Erstellen neuer Profile und Bewerbungen ermöglicht.
* Einen **privaten Schlüssel**, der alles ermöglicht. Dieser Schlüssel wird Ihnen nur einmal bei der Erstellung angezeigt. Denken Sie daran, ihn an einem sicheren Ort zu notieren.
Der private Schlüssel sollte nicht weitergegeben oder im JavaScript-Code enthalten sein, der für den Benutzer
zugänglich ist.
# [Bewerbung] Erstellen oder aktualisieren
Source: https://api-docs.jarvi.tech/de/api-reference/applicants/post
POST /rest/v2/applicants
Save a new job application in Jarvi. It creates the profile if it does not exist, upload the file, link the profile to the recruitment project,...
Sie können Ihren **öffentlichen API-Schlüssel** für diese Anfrage verwenden. Das ermöglicht es Ihnen, dies direkt im
Javascript-Code der Website zu tun.
Die maximale Größe der Anfrage beträgt 4,5 MB. Wir empfehlen Ihnen, die Größe Ihres Lebenslauf-Upload-Feldes auf eine
kleinere Größe zu begrenzen.
Sie können alles im Profil über Benutzerdefinierte Felder speichern. Klicken Sie auf diesen Link, um zu verstehen, wie Sie Informationen in benutzerdefinierten Feldern lesen und schreiben können.
# [Unternehmen] Auflisten
Source: https://api-docs.jarvi.tech/de/api-reference/companies/get
GET /rest/v2/companies
Get all companies (recruitment, sales, etc.) with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Sie müssen Ihren **privaten API-Schlüssel** für diese Anfrage verwenden.
Für dieses API sind im Sommer 2025 Änderungen geplant. Es ist daher möglich, dass aufgrund von Weiterentwicklungen
Anpassungen auf Ihrer Seite erforderlich sein werden.
Sie können die Informationen jedes Profils vollständig mit Benutzerdefinierten Feldern anpassen. Klicken Sie auf diesen Link, um zu verstehen, wie Sie Informationen in benutzerdefinierten Feldern lesen und schreiben können.
# [Unternehmen] Erstellen oder aktualisieren
Source: https://api-docs.jarvi.tech/de/api-reference/companies/post
POST /rest/v2/companies
Save a company in Jarvi. It creates the company if it does not exist, updates it if it does.
Sie müssen Ihren **privaten API-Schlüssel** für diese Anfrage verwenden.
Sie können alle Informationen jedes Profils vollständig mit Benutzerdefinierten Feldern anpassen. Klicken Sie auf diesen Link, um zu verstehen, wie Sie Informationen in benutzerdefinierten Feldern lesen und schreiben können.
# GraphQL
Source: https://api-docs.jarvi.tech/de/api-reference/graphql
POST /graphql
Die vollständige API für alles andere!
Diese API ermöglicht es dir, absolut alles zu tun, was du dir wünschst. Du kannst deine Anfragen anpassen, um genau das zu lesen/schreiben, was du brauchst.
Wir garantieren nicht, dass die in dieser API verfügbaren Informationen sich nicht ohne Vorankündigung ändern. Wir
empfehlen dir, diese API nicht für kritische Anwendungen zu nutzen, ohne vorher mit uns zu sprechen.
Der Editor bietet dir eine Oberfläche, die alle Möglichkeiten aufzeigt und dir erlaubt, deine Anfragen in Echtzeit
zu testen.
Hier siehst du in einem Video, wie du den Query-Editor verwendest:
# [Angebote] Auflisten
Source: https://api-docs.jarvi.tech/de/api-reference/offers/get
GET /rest/v2/offers
Get all offers with their custom fields, files, and more.
Sie müssen Ihren **öffentlichen API-Schlüssel** für diese Anfrage verwenden. Sie müssen die Daten auf Ihrer Seite
zwischenspeichern, um die täglichen Aufrufbeschränkungen einzuhalten.
Sie können alle Informationen jedes Projekts vollständig mit Benutzerdefinierten Feldern anpassen. Klicken Sie auf diesen Link, um zu verstehen, wie Sie Informationen in benutzerdefinierten Feldern lesen und schreiben können.
# [Profile] Auflisten
Source: https://api-docs.jarvi.tech/de/api-reference/profiles/get
GET /rest/v2/profiles
Get all profiles with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Sie müssen Ihren **privaten API-Schlüssel** für diese Anfrage verwenden.
Sie können alle Informationen jedes Profils vollständig mit Benutzerdefinierten Feldern anpassen. Klicken Sie auf diesen Link, um zu verstehen, wie Sie Informationen in benutzerdefinierten Feldern lesen und schreiben können.
# [Profile] Erstellen oder aktualisieren
Source: https://api-docs.jarvi.tech/de/api-reference/profiles/post
POST /rest/v2/profiles
Save a profile in Jarvi. It creates the profile if it does not exist, updates it if it does.
Sie müssen Ihren **privaten API-Schlüssel** für diese Anfrage verwenden.
Sie können alle Informationen jedes Profils vollständig mit Benutzerdefinierten Feldern anpassen. Klicken Sie auf diesen Link, um zu verstehen, wie Sie Informationen in benutzerdefinierten Feldern lesen und schreiben können.
# [Projekte] Auflisten
Source: https://api-docs.jarvi.tech/de/api-reference/projects/get
GET /rest/v2/projects
Get all projects (recruitment, sales, etc.) with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Sie müssen Ihren **privaten API-Schlüssel** für diese Anfrage verwenden. Sie müssen die Daten auf Ihrer Seite
zwischenspeichern, um die täglichen Anfragelimits einzuhalten.
Sie können alle Informationen jedes Projekts vollständig mit Benutzerdefinierten Feldern anpassen. Klicken Sie auf diesen Link, um zu verstehen, wie Sie Informationen in benutzerdefinierten Feldern lesen und schreiben können.
# [Projekte] Erstellen oder aktualisieren
Source: https://api-docs.jarvi.tech/de/api-reference/projects/post
POST /rest/v2/projects
Save a project in Jarvi. It creates the project if it does not exist, updates it if it does.
Sie müssen Ihren **privaten API-Schlüssel** für diese Anfrage verwenden.
Sie können alle Informationen jedes Profils vollständig mit benutzerdefinierten Feldern anpassen. Klicken Sie auf diesen Link, um zu verstehen, wie Sie Informationen in benutzerdefinierten Feldern lesen und schreiben können.
# Benutzerdefinierte Felder
Source: https://api-docs.jarvi.tech/de/custom-fields
Das Verständnis des Konzepts der benutzerdefinierten Felder ermöglicht es Ihnen, das volle Potenzial von Jarvi und dieser API auszuschöpfen
Jarvi-Benutzer können die verfügbaren Eingabefelder konfigurieren für
* Profile (Kandidaten oder Interessenten)
* Projekte (Rekrutierung oder Akquise)
* Unternehmen
Jedes Feld wird durch eine eindeutige ID (UUID) identifiziert.
Folgen Sie diesem Link, um auf Jarvi zuzugreifen und eine Liste aller verfügbaren Eingabefelder für die
Personalvermittlung zu erhalten.
Mit der API können Sie Informationen aus diesen Feldern abrufen und auch ändern.
Dafür müssen Sie nur die eindeutige ID (UUID) kennen.
Wenn Sie auf den Link unten klicken und dann ein Feld öffnen, finden Sie seine ID am Ende der URL.
Zum Beispiel, bei `https://app.jarvi.tech/#/settings/fields/cdccbea3-debd-453d-8a30-691f48c5a9e5` ist die ID `cdccbea3-debd-453d-8a30-691f48c5a9e5`.
# Einführung
Source: https://api-docs.jarvi.tech/de/introduction
Willkommen bei der Dokumentation der Jarvi-API
## Warum die Jarvi-API verwenden?
Diese API ermöglicht es dir, absolut alles zu tun, was du dir wünschst. Dieselbe API wird von der Jarvi-Anwendung und ihrer Erweiterung verwendet.
Hier sind die häufigsten Anwendungsfälle:
Sende Bewerbungen von deiner Website, aus Formularen oder anderen Quellen direkt an Jarvi.
Rufe Projekte, in Jarvi erstellte Stellenangebote und alle zugehörigen Informationen ab.
Aber vergiss nicht, alles ist möglich.
## Nutzungsbedingungen
* Die Jarvi-API wendet Grenzen basierend auf der **kumulierten Ausführungszeit** deiner Anfragen an, nicht auf deren Anzahl.
* Diese Grenzen gelten **nur von Montag bis Freitag, von 07:00 bis 18:00 UTC** (06:00–17:00 UTC während der Sommerzeit). Außerhalb davon — nachts und am Wochenende — gilt keinerlei Begrenzung: der ideale Zeitpunkt für deine Batch-Verarbeitungen.
* Während der Geschäftszeiten ist deine Nutzung auf **5 Minuten** kumulierte Ausführungszeit pro Stunde begrenzt.
* Diese Grenzen sind sehr großzügig bemessen und entsprechen etwa dem **100-fachen des durchschnittlichen Verbrauchs** eines Jarvi-Nutzers.
* Bei Überschreitung gibt die API einen `429`-Fehler mit Details zu deinem Verbrauch sowie einen `Retry-After`-Header zurück.
* Wenn du ein höheres Limit benötigst, kontaktiere unseren Support — wir können die Schwellenwerte an deinen Bedarf anpassen.
* Du verpflichtest dich, die Vorschriften zum Schutz personenbezogener Daten, insbesondere die DSGVO, für alle über die API gelesenen/geschriebenen Daten einzuhalten.
## Schnellstart
Deine API-Schlüssel ermöglichen dir den Zugriff auf die API
# MCP (KI-Assistenten)
Source: https://api-docs.jarvi.tech/de/mcp
Verbinde KI-Assistenten wie Claude Desktop oder Cursor mit deinen Jarvi-Daten
## Was ist MCP?
Das [Model Context Protocol (MCP)](https://modelcontextprotocol.io) ist ein offener Standard, der es KI-Assistenten ermöglicht, sich mit externen Tools und Datenquellen zu verbinden. Jarvi stellt einen MCP-Server bereit, damit du KI-Assistenten nutzen kannst, um deine Jarvi-Daten direkt zu durchsuchen, zu lesen und zu aktualisieren.
## Kompatible Clients
Jeder MCP-kompatible Client kann sich mit Jarvi verbinden, darunter:
* [Claude Desktop](https://claude.ai/download)
* [Cursor](https://cursor.com)
* [Windsurf](https://codeium.com/windsurf)
* Jeder Client, der **Streamable HTTP**-Transport unterstützt
## Konfiguration
### Claude Desktop
Gehe zu **Einstellungen > Konnektoren > Benutzerdefinierten Konnektor hinzufügen**, gib einen Namen ein (z. B. "Jarvi") und die URL:
```
https://mcp.jarvi.tech
```
Claude Desktop übernimmt die Authentifizierung automatisch über OAuth2 – du wirst zur Anmeldung mit deinem Jarvi-Konto weitergeleitet.
### Andere Clients
Für Clients, die OAuth2 nicht unterstützen, verwende einen privaten API-Schlüssel:
* **Transport**: Streamable HTTP
* **URL**: `https://mcp.jarvi.tech`
* **Authentifizierung**: Header `x-api-key` mit deinem [privaten API-Schlüssel](./api-keys)
Dein privater Schlüssel ermöglicht vollen Zugriff auf deine Jarvi-Daten. Teile ihn niemals öffentlich.
## Verfügbare Tools
Sobald du verbunden bist, stehen deinem KI-Assistenten folgende Tools zur Verfügung:
| Tool | Beschreibung |
| ------------------------- | -------------------------------------------------------------------------------------------------- |
| `whoAmI` | Gibt die Identität des angemeldeten Benutzers zurück (Name, E-Mail, Workspace) |
| `getStatuses` | Listet verfügbare Status für Profile, Unternehmen oder Projekte auf |
| `getCustomFields` | Listet benutzerdefinierte Felder für einen Entitätstyp auf |
| `getHistorySubtypes` | Listet benutzerdefinierte Interaktions-Untertypen auf (z. B. Anrufkategorien) |
| `advancedSearch` | Sucht Profile, Unternehmen oder Projekte in natürlicher Sprache |
| `getEntityData` | Ruft detaillierte Daten von Profilen, Unternehmen oder Projekten anhand von IDs ab |
| `getAgenda` | Ruft bevorstehende Ereignisse aus verbundenen Kalendern ab |
| `getNotifications` | Ruft aktuelle Benachrichtigungen des Benutzers ab |
| `createProfile` | Erstellt ein neues Profil (Talent und/oder Kontakt) |
| `createCompany` | Erstellt ein neues Unternehmen |
| `createProject` | Erstellt ein neues Projekt / Recruiting-Mission |
| `associateProfiles` | Verknüpft ein Profil mit einem Projekt (Talent oder Kontakt) oder mit einem Unternehmen |
| `createHistoryEntry` | Fügt eine Notiz oder ein Anrufprotokoll zum Verlauf eines Profils hinzu |
| `createTodo` | Erstellt eine Erinnerung/Todo, die mit einem Profil, Unternehmen oder Projekt verknüpft ist |
| `sendMessage` | Sendet Nachrichten an ein oder mehrere Profile (E-Mail, LinkedIn, WhatsApp, Telegram, SMS) |
| `getMessageTemplates` | Listet verfügbare Nachrichtenvorlagen auf (persönliche und von Kollegen geteilte) |
| `uploadFile` | Lädt eine Datei hoch und verknüpft sie mit einer Entität (Profil, Unternehmen oder Projekt) |
| `cancelScheduledMessages` | Bricht eine oder mehrere geplante Nachrichten ab |
| `updateProfile` | Aktualisiert ein Profil, fügt E-Mails oder Telefonnummern hinzu |
| `updateCompany` | Aktualisiert ein Unternehmen, ändert den Status nach Name, fügt E-Mails hinzu |
| `updateProject` | Aktualisiert ein Projekt, ändert den Status nach Name, verknüpft ein Unternehmen |
| `fillJobOffer` | Erstellt oder verfeinert ein Stellenangebot basierend auf Projektdaten |
| `getOutboxDiagnostic` | Diagnostiziert den Postausgang: Listet ausstehende geplante Nachrichten mit Status und Fehlern auf |
| `retryOutboxTasks` | Startet fehlgeschlagene Aufgaben neu, indem ihr Status zurückgesetzt wird |
| `queryGraphQL` | Führt GraphQL-Abfragen/-Mutationen auf erlaubten Tabellen aus (deine Berechtigungen gelten) |
Alle Vorgänge werden mit **deinen Berechtigungen** ausgeführt. Die Sicherheit auf Zeilenebene gilt – du hast nur Zugriff auf Daten, auf die dein Konto zugreifen darf.
## Beispiele für Prompts
Sobald du verbunden bist, versuche deinen KI-Assistenten zu fragen:
* *"Wer bin ich?"*
* *"Suche React-Entwickler in Paris"*
* *"Zeig mir die Details von Projekt X"*
* *"Erstelle ein Profil für Jean Dupont, Entwickler"*
* *"Platziere diesen Kandidaten auf Projekt X"*
* *"Setze den Status von Firma Acme auf Qualifiziert"*
* *"Welche benutzerdefinierten Felder gibt es in den Profilen?"*
* *"Notiere einen Anruf mit Jean Dupont: Verfügbarkeit besprochen"*
* *"Erinnere mich daran, Acme nächsten Montag erneut zu kontaktieren"*
* *"Aktualisiere den Titel von Jean Dupont auf Senior-Entwickler"*
## Einschränkungen
* Es gelten dieselben [Nutzungsbeschränkungen](./introduction#nutzungsbedingungen), gerechnet auf die kumulierte Ausführungszeit. Wenn du mehr brauchst, wende dich an unseren Support.
* Löschvorgänge sind blockiert — verwende stattdessen Soft-Delete (Aktualisierung von `deletedAt`).
* Zu umfangreiche Antworten werden automatisch gekürzt. Verwende `limit`-Argumente in deinen Anfragen.
* Destruktive Tools (Mutationen, Kampagnenstart, Nachrichtenwiederholung) erfordern vor der Ausführung eine Bestätigung durch den KI-Assistenten.
# Suche
Source: https://api-docs.jarvi.tech/de/search
Profile, Unternehmen und Projekte über indizierte Felder mit dem Operator _search filtern
## Warum `_search`
Jarvi betreibt eine Volltextsuchmaschine über deine Daten. Namen, E-Mail-Adressen, Telefonnummern, Jobtitel, Kompetenzen und Werte eigener Felder sind indiziert, und `_search` fragt diese Indizes ab.
Mit `_ilike "%Begriff%"` zu filtern macht etwas völlig anderes: PostgreSQL geht jeden Datensatz deines Arbeitsbereichs durch und vergleicht ihn mit dem Muster. Auf einer Produktivdatenbank wurde ein einzelnes `_ilike` auf einen Nachnamen mit 2,4 Sekunden gemessen; dieselbe Suche über den Index antwortet in einigen zehn Millisekunden.
`_ilike`, `_like`, `_nilike` und `_nlike` werden am **31. Juli 2027** aus der öffentlichen API
entfernt.
Bei den REST-Endpunkten musst du nichts tun — `_ilike`-Filter werden bereits für dich in eine
indizierte Suche übersetzt, und die Antwort nennt den Filter, der deinen ersetzt hat. Über den
Endpunkt `/graphql` gehört die Abfrage dir: sie kann jede Tabelle ansprechen, also änderst du sie
selbst.
## Den Operator verwenden
Ein Filter besteht aus dem Namen eines Suchziels und einem Begriff:
```json theme={null}
{ "quickSearch": { "_search": "Dupont" } }
```
Kombiniere mehrere mit `_and`, `_or` und `_not`, wie bei jedem anderen Filter:
```json theme={null}
{
"_and": [
{ "globalSearch": { "_search": "javascript" } },
{ "isTalent": true },
{ "addedAt": { "_gte": "2026-01-01" } }
]
}
```
### Abfragesyntax
Der Begriff ist selbst eine Abfrage, keine einfache Zeichenkette:
| Syntax | Trifft auf |
| -------------------------------- | ---------------------------------------------------------------- |
| `developer designer` | eines der beiden Wörter |
| `developer AND designer` | beide Wörter |
| `developer AND (java OR kotlin)` | beide, mit einer der beiden Technologien |
| `NOT intern` | Datensätze ohne dieses Wort |
| `"project manager"` | genau diese Wortfolge, in dieser Reihenfolge |
| `java^10 python` | beide Wörter, wobei java in der Rangfolge zehnmal schwerer wiegt |
### Ganze Wörter, keine Fragmente
Namen werden Wort für Wort vom Anfang her indiziert: `Dupo` findet *Dupont*, `upont` nicht. Dasselbe gilt für Unternehmens- und Projektnamen.
E-Mail-Adressen und Telefonnummern sind anders indiziert und treffen ein Fragment an beliebiger Stelle: eine Nummer findest du über ihre letzten Ziffern, eine E-Mail über ihre Domain.
## Die Filter, Ressource für Ressource
Alle folgenden Filter stützen sich auf einen Index. Andere Hasura-Spalten und -Beziehungen werden weiterhin akzeptiert, sind aber weder optimiert noch garantiert.
### Profiles
| Filter | Operators | What it matches |
| -------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quickSearch` | `_search` | Searches name, email, phone and short id — the target for simple person or name lookups. |
| `globalSearch` | `_search` | Searches name, headline, summary, position titles, company names, skills, educations and custom field values — the target for skill, role or technology searches. |
| `emailPhoneAndNameSearch` | `_search` | Searches name, email, phone and short id — the target for simple person or name lookups. |
| `fullPositionTitleSkillsAndTagsSearch` | `_search` | Searches name, headline, summary, position titles, company names, skills, educations and custom field values — the target for skill, role or technology searches. |
| `headline` | `_search` | Headline of the profile: the tagline shown under the name on LinkedIn, or the summary line extracted from the CV for profiles that have no LinkedIn page. Editable by hand only when the profile has no LinkedIn identifier. |
| `currentPositions.global` | `_search` | Full-text over title, description |
| `currentPositions.global.title` | `_search` | Job title held at this position, coming from LinkedIn, from a parsed CV or entered by hand. This is the main field behind job title search. |
| `currentPositions.global.description` | `_search` | What the person wrote about this role on their profile or CV. Often empty on imported records. |
| `emails` | `_search` | Email address of the profile or the company it belongs to. Normalized on save: lowercased, and when the input is not a clean address the first valid one found inside it is kept, the row being dropped if there is none. |
| `phones` | `_search` | Phone number search. Only the digits are compared, so a number matches whatever spacing, separators or country prefix it was typed with, and a suffix matches too. |
| `profiles.shortId` | `_search` | Short readable reference for the profile — what users type in quick search and what appears on generated documents. Assigned once at creation and never changes. |
| `historyEntries` | `_search` | Full-text over subject, message |
| `historyEntries.subject` | `_search` | Subject of the interaction: the email subject, the meeting title, or the job offer title. Empty for channels that carry no subject, such as LinkedIn messages, WhatsApp and SMS. |
| `historyEntries.message` | `_search` | Content of the interaction: the email body, the text of the LinkedIn, WhatsApp, SMS or Telegram message, or what the recruiter typed for a manual note. |
| `historyEntries.type` | `_eq`, `_in`, `_is_null` | What kind of interaction this entry records: emails, LinkedIn messages and InMails, connection invitations, SMS, WhatsApp, Telegram, phone calls, scheduled meetings, job applications, or a free note. |
| `historyEntries.subTypeId` | `_eq`, `_in`, `_is_null` | Optional sub-category of the interaction, defined by the group itself — naming its own kinds of notes or calls, for instance. Each sub-category belongs to a single parent type. |
| `historyEntries.occurredAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Single date used to place the interaction on the timeline: the meeting start for a scheduled event, the creation date otherwise. Maintained by a trigger. |
| `historyEntries.userId` | `_eq`, `_in`, `_is_null` | Jarvi user this entry belongs to. Determines the group the entry is attached to, and is used as its sender by default. |
| `historyEntries.hasFiles` | `true`, `false` | The profile has at least one interaction carrying an attached file. |
| `historyEntries.hasRecording` | `true`, `false` | The profile has at least one interaction with a meeting recording. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `location` | `_near` | Geographic search. Use range 0 for an exact city or region match, or a radius in meters. |
| `hasEmail` | `true`, `false` | The profile has at least one email address. |
| `hasEmailType` | `_eq`, `_in`, `_is_null` | The profile has an email address of this type. Takes the same values as the type of an email address. |
| `hasPhone` | `true`, `false` | The profile has at least one phone number. |
| `hasValidPhone` | `true`, `false` | The profile has at least one phone number not marked as invalid. |
| `hasLocation` | `true`, `false` | The profile has at least one location. |
| `hasPhoneType` | `_eq`, `_in`, `_is_null` | The profile has a phone number of this type. Takes the same values as the type of a phone number. |
| `hasWhatsApp` | `true`, `false` | The profile has a phone number reachable on WhatsApp. |
| `isTalent` | `true`, `false` | The profile is a talent — someone you recruit, followed in the ATS. A profile can be a talent and a contact at once. |
| `isContact` | `true`, `false` | The profile is a contact — a client or a hiring manager, followed in the CRM. A profile can be a contact and a talent at once. |
| `hasLinkedin` | `true`, `false` | The profile is matched to a LinkedIn account. |
| `hasResume` | `true`, `false` | The profile has at least one CV attached. |
| `isConnectedOnLinkedinWithMe` | `true`, `false` | The calling user is connected with this profile on LinkedIn, as recorded in the profile's history. |
| `hasReplied` | `true`, `false` | The profile has answered at least once, on any channel. |
| `hasScheduledMessage` | `true`, `false` | A message or a connection invitation is still waiting to be sent to this profile. |
| `hasUnreadMessagesNotifications` | `true`, `false` | The profile has an unread message notification. |
| `firstPositionStartDate` | `_eq`, `_gte`, `_lte`, `_is_null` | Start date of the earliest position on the profile — the usual way to reason about years of experience. |
| `lastPositionStartDate` | `_eq`, `_gte`, `_lte`, `_is_null` | Start date of the current position, that is the one with no end date. |
| `currentPositions.companyId` | `_eq`, `_in`, `_is_null` | The profile currently works at this company. Takes the identifier of the LinkedIn company page, not a Jarvi company id. |
| `projectId` | `_eq`, `_in`, `_is_null` | The profile is associated with this project. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the profile. A parent status also matches profiles carrying any of its children, and the search is narrowed by the project or entity given in the context. Pass "other" for profiles whose status has no parent. |
| `hasProject` | `true`, `false` | The profile is associated with at least one project. |
| `projectCount` | `_eq`, `_gte`, `_lte`, `_is_null` | How many distinct projects the profile is associated with. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `isInCrm` | `true`, `false` | The profile is followed in the CRM, meaning it is a contact. Same as isContact. |
| `isInAts` | `true`, `false` | The profile is followed in the ATS, meaning it is a talent. Same as isTalent. |
| `hasAttachedFiles` | `true`, `false` | The profile has at least one attached file. |
| `isOpenToNewOpportunities` | `true`, `false` | The profile is flagged as open to new opportunities. |
| `isHiring` | `true`, `false` | The profile is flagged as hiring. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the profile, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Technical timestamp of the row creation. For the date the profile entered the base, use addedAt (added\_at) instead. |
| `addedAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the profile entered the group's base — what recruiters read as "added on". Set automatically when the profile is created or restored, unless the caller provides its own value, which is what imports do to keep the original date. |
| `externalId` | `_eq`, `_in` | Identifier of the profile in the client's own system — their ATS, their CRM, or whatever calls the public API. Never a Jarvi identifier. Unique per group, and used to recognise a profile that is sent again. |
| `publicIdentifier` | `_eq`, `_in` | LinkedIn public slug, the part after linkedin.com/in/. Unique per group. The person can change it on LinkedIn, which makes it the least reliable of the LinkedIn identifiers. |
| `linkedinId` | `_eq`, `_in` | Numeric LinkedIn identifier of the person. Unique per group. Not to be confused with memberId (member\_id), which holds the obfuscated "ACoAA…" identifier, nor with publicIdentifier (public\_identifier), which holds the URL slug. |
| `primaryLocale` | `_eq`, `_in`, `_is_null` | Main language of the profile itself — the language its LinkedIn page or CV is written in. Different from the profiles\_locales table, which lists the languages the person speaks and how well. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
### Companies
| Filter | Operators | What it matches |
| ---------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `companySearch` | `_search` | Searches company name and description, LinkedIn tagline and description, legal activity and NAF code, and custom fields — the target for keyword or industry searches. |
| `companyQuickSearch` | `_search` | Searches company name, email and phone — for name lookups only. |
| `companies.global` | `_search` | Company name and description. |
| `location` | `_near` | Geographic search. Use range 0 for an exact city or region match, or a radius in meters. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `hasLinkedinCompanyId` | `true`, `false` | The company is matched to a LinkedIn company page. |
| `hasContacts` | `true`, `false` | The company has at least one profile associated as a contact. |
| `hasProject` | `true`, `false` | The company has at least one project. |
| `isParentCompany` | `true`, `false` | The company has at least one subsidiary. |
| `isSubsidiary` | `true`, `false` | The company has a parent company. |
| `isProtected` | `true`, `false` | The company is under protection, meaning its protection date is still in the future. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the company. A parent status also matches companies carrying any of its children. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the company, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the company record was created. Data imports replace it with the creation date coming from the source ATS or CRM. |
| `externalId` | `_eq`, `_in` | Identifier of the company in the client's own system. Unique per group, and used as the matching key by imports and by the public API. |
| `linkedinCompanyId` | `_eq`, `_in` | Link to the LinkedIn page data of this company. That data is shared across all groups, and a given LinkedIn page can be attached to at most one company per group. |
| `parentId` | `_eq`, `_in`, `_is_null` | Parent company when this record is a subsidiary. Used as a single level: searching on a parent can include its direct subsidiaries, but not their own. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
### Projects
| Filter | Operators | What it matches |
| --------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectNameSearch` | `_search` | Searches project name, its company name and its reference id — the target for project lookups. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `hasJobBoardEnabled` | `true`, `false` | The project is published on at least one job board. |
| `hasLinkedinId` | `true`, `false` | The project is matched to a LinkedIn job posting. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the project. A parent status also matches projects carrying any of its children. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the project, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the project was created. Data imports replace it with the creation date coming from the source ATS or CRM. |
| `externalId` | `_eq`, `_in` | Identifier of the project in the client's own system. Unique per group, and used as the matching key by imports and by the public API. |
| `referenceId` | `_eq`, `_in` | The project reference id is used to map external job applications on website or jobboard to the project. |
| `companyId` | `_eq`, `_in`, `_is_null` | Client company the project is for. Optional — a project can exist without a company, and deleting the company clears the link rather than the project. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
Diese Listen werden von der Suchmaschine selbst erzeugt und entsprechen damit immer dem, was die
API akzeptiert. Dieselben Definitionen werden als JSON Schema von [GET
/openapi](https://functions.prod.jarvi.tech/v1/public-api/openapi) ausgeliefert, unter
`ProfilesFilters`, `CompaniesFilters` und `ProjectsFilters`.
## Sortieren
Der Parameter `orderBy` jedes REST-Endpunkts verbindet ein Feld mit einer Richtung:
```
?orderBy={"addedAt":"desc"}
```
Welche Felder eine Ressource akzeptiert, steht in der Dokumentation des Parameters selbst, auf der Seite des Endpunkts. Nach etwas anderem zu sortieren fällt auf die Standardreihenfolge zurück, statt zu scheitern.
## Über GraphQL suchen
Die Query `search` liefert Bezeichner und eine Gesamtzahl, die du anschließend mit einem zweiten Aufruf lädst. Diese Aufteilung in zwei Schritte hält eine Suche schnell, unabhängig von der Größe der Datensätze:
```graphql theme={null}
query FindDevelopers {
search(
resource: "profiles"
filters: { globalSearch: { _search: "javascript AND react" }, isTalent: true }
sort: { field: "addedAt", order: "DESC" }
limit: 50
) {
ids
total
}
}
```
```graphql theme={null}
query LoadThem($ids: [uuid!]!) {
profiles(where: { id: { _in: $ids } }) {
id
firstName
lastName
}
}
```
`resource` ist `profiles`, `companies` oder `projects`. `filters` nimmt dasselbe Objekt wie der REST-Parameter `where`.
## Weg von `_ilike`
| Statt | Schreibe |
| --------------------------------------------------------- | --------------------------------------------- |
| `{"lastName": {"_ilike": "%Dupont%"}}` | `{"quickSearch": {"_search": "Dupont"}}` |
| `{"emailAddresses": {"email": {"_ilike": "%@acme.com"}}}` | `{"emails": {"_search": "acme.com"}}` |
| `{"headline": {"_ilike": "%developer%"}}` | `{"globalSearch": {"_search": "developer"}}` |
| `{"name": {"_ilike": "%Acme%"}}` (Unternehmen) | `{"companyQuickSearch": {"_search": "Acme"}}` |
| `{"externalId": {"_ilike": "ABC-123"}}` | `{"externalId": {"_eq": "ABC-123"}}` |
Ein Bezeichner oder eine URL ist nie eine Suche: nimm `_eq`, das über seinen eigenen Index läuft.
Fehlt dir etwas, oder findest du einen erwarteten Filter nicht? Schreib uns — die Liste oben wird
von der Suchmaschine gesteuert, einen Filter hinzuzufügen ist eine kleine Änderung.
# Bewerbungen senden
Source: https://api-docs.jarvi.tech/de/use-cases/applicants
Diese API ermöglicht es dir, Kandidaten von deiner Website, einem Formular oder jeder anderen Quelle an Jarvi zu übermitteln.
So gehen Sie vor:
Wenn Sie Ihre API-Schlüssel noch nicht haben, folgen Sie der Anleitung im Abschnitt
API-Schlüssel erhalten
Lesen Sie die detaillierte Dokumentation der API post applicants. Es wird auch hilfreich sein, das Konzept der Benutzerdefinierten Felder zu verstehen
Keine Sorge, Sie sind nicht der Erste, und Sie werden wahrscheinlich unten Beispiele für Integrationen finden,
die Ihren Wünschen ähnlich sind.
Hier ist ein Beispiel für eine Wordpress-Integration mit dem QuForm-Plugin für Formulare.
```php theme={null}
add_action('quform_post_process_{ID_FORM}', function (array $result, Quform_Form $form){
$file_content = file_get_contents( $this->attachments[ 0 ] );
$file_base_64_content = base64_encode( $file_content );
$data = [
'firstName' => $form->getValue('quform_{ID_FORM}_5'),
'lastName' => $form->getValue('quform_{ID_FORM}_7'),
'fullName' => $form->getValue('quform_{ID_FORM}_6') . ' ' . $form->getValue('quform_{ID_FORM}_7'),
'cb9d4872-bfaa-4c57-b1af-91c008883386' => $form->getValue('quform_{ID_FORM}_10'), // Poste
'17fe3ebf-3641-4a94-ac9f-f11a6d429107' => $form->getValue('quform_{ID_FORM}_31'), // Exp SaaS
'6b751e8d-3947-43f9-af52-1db532210c6d' => $form->getValue('quform_{ID_FORM}_12'), // 1e année CDI
'region' => $form->getValue('quform_{ID_FORM}_15'),
'linkedinUrl' => $form->getValue('quform_{ID_FORM}_17'),
'phoneNumbers' => $form->getValue('quform_{ID_FORM}_20'),
'emailAddresses' => $form->getValue('quform_{ID_FORM}_22'),
'resumesFiles' => [
[
'data' => $file_base_64_content,
'fileName' => $this->attachments[0]->file_name,
],
],
];
wp_remote_post('https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants', [
'body' => json_encode($data),
'headers' => array(
'Content-Type' => 'application/json',
'X-Api-Key' => '{JARVI_PRIVATE_API_KEY}'
),
]);
return $result;
}, 10, 2);
```
Hier ist ein Beispiel für eine Integration in Webflow. Diese Integration funktioniert auch für eine klassische Website.
```javascript theme={null}
document.querySelector('#form-submit-button').addEventListener('click', function() {
var formElement = document.querySelector('.inscription-candidat-form');
var formData = new FormData(formElement);
var formDataAsObject = {}
for (var [key, value] of formData.entries()) {
console.log(key, value);
formDataAsObject[key] = value
}
if(!formDataAsObject.linkedinUrl) console.error("linkedinUrl is mandatory");
const data = {
"referenceId":"PROJECT-REFERENCE-ID",
...formDataAsObject
}
console.log("data to send",data)
fetch('https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants', {
method: 'POST',
mode:'cors',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'JARVI_PUBLIC_API_KEY'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => console.log("data sent",data))
.catch((error) => {
console.error('Error:', error);
});
});
```
Hier ist ein Beispiel für eine Integration in Webflow. Diese Integration funktioniert auch für eine klassische Website.
```javascript theme={null}
// Fill those constants with your own values
const FORM_SELECTOR = "#candidature-form";
const SUBMIT_BUTTON_SELECTOR = "#form-submit-button";
const RESUME_INPUT_NAME = "resumesFiles";
const PROJECT_REFERENCE_ID = "PROJECT-REFERENCE-ID";
const JARVI_PUBLIC_API_KEY = "YOUR JARVI PUBLIC API KEY";
// Helper function to read file as base64
const readFileAsBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => resolve(event.target.result);
reader.onerror = (error) => reject(error);
reader.readAsDataURL(file);
});
};
// Helper function to validate form data
const validateFormData = (formData) => {
if (!formData.linkedinUrl) {
throw new Error("LinkedIn URL is mandatory");
}
return true;
};
// Main form submission handler
document.querySelector(SUBMIT_BUTTON_SELECTOR).addEventListener("click", async function (event) {
event.preventDefault(); // Prevent default form submission
try {
const formElement = document.querySelector(FORM_SELECTOR);
if (!formElement) {
throw new Error("Form element not found");
}
// Convert FormData to object
const formData = new FormData(formElement);
const formDataAsObject = Object.fromEntries(formData.entries());
// Validate form data
validateFormData(formDataAsObject);
// Get and validate file
const fileInput = formElement.querySelector(`input[name="${RESUME_INPUT_NAME}"]`);
if (!fileInput) {
throw new Error("Resume input field not found");
}
const file = fileInput.files[0];
if (!file) {
throw new Error("No file uploaded");
}
// Read file and prepare data
const base64Data = await readFileAsBase64(file);
const resumesFiles = [
{
fileName: file.name,
data: base64Data,
},
];
const data = {
referenceId: "PROJECT-REFERENCE-ID", // Update with your project reference ID
...formDataAsObject,
resumesFiles,
};
console.log("Sending data:", data);
// Send data to API
const response = await fetch("https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
"X-Api-Key": JARVI_PUBLIC_API_KEY,
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
console.log("Data sent successfully:", responseData);
// Optional: Show success message to user
alert("Application submitted successfully!");
} catch (error) {
console.error("Error:", error);
alert(`Error: ${error.message}`);
}
});
```
# Projekte abrufen
Source: https://api-docs.jarvi.tech/de/use-cases/projects
Diese API ermöglicht es dir, alle Rekrutierungsprojekte sowie Akquiseprojekte und alle zugehörigen Informationen abzurufen. Das Ziel ist oft, diese auf deiner Website anzuzeigen.
Hier ist die Vorgehensweise:
Wenn du deine API-Schlüssel noch nicht hast, folge der Anleitung im Abschnitt
API-Schlüssel erhalten
Sieh dir die detaillierte Dokumentation der API GET projects an. Es wird
dir sicherlich auch helfen, das Konzept der Benutzerdefinierten Felder zu verstehen
Du hast die Projekte abgerufen, vielleicht möchtest du auch Bewerbungen an Jarvi senden:
Anwendungsfall: Bewerbungen an Jarvi senden
# Get my API keys
Source: https://api-docs.jarvi.tech/en/api-keys
Your API keys will allow you to access the API
Go to your preferences, then to API. Click on the button to generate API keys as shown below.
You will then get 2 keys:
* A **public key**, which only allows you to create new profiles and new applications.
* A **private key**, which allows you to do everything. This key will only be shown to you once, when it's created. So remember to save it in a safe place.
The private key should not be disclosed, nor present in javascript code accessible to the user.
# [Application] Create or update
Source: https://api-docs.jarvi.tech/en/api-reference/applicants/post
POST /rest/v2/applicants
Save a new job application in Jarvi. It creates the profile if it does not exist, upload the file, link the profile to the recruitment project,...
You can use your **public api key** for this request. This allows you to do it directly in your website's javascript
code.
The maximum request size is 4.5MB. We recommend limiting your CV upload field size to a smaller size.
You can save everything on the profile using Custom fields. Click on this link to understand how to read and write information in custom fields.
# [Companies] List
Source: https://api-docs.jarvi.tech/en/api-reference/companies/get
GET /rest/v2/companies
Get all companies (recruitment, sales, etc.) with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
You must use your **private api key** for this request.
Changes are planned for this API in summer 2025. Therefore, you may need to make adjustments on your end following
these updates.
You can completely customize all information for each profile using Custom fields. Click on this link to understand how to read and write information in custom fields.
# [Company] Create or update
Source: https://api-docs.jarvi.tech/en/api-reference/companies/post
POST /rest/v2/companies
Save a company in Jarvi. It creates the company if it does not exist, updates it if it does.
You must use your **private api key** for this request.
You can completely customize all information for each profile using Custom fields. Click on this link to understand how to read and write information in custom fields.
# GraphQL
Source: https://api-docs.jarvi.tech/en/api-reference/graphql
POST /graphql
The complete API to do everything else!
This API allows you to do absolutely anything you wish. You can customize your requests to read/write whatever you need.
We do not guarantee that the information available in this API won't change without notice. We advise you not to use
this API for critical applications without discussing it with us first.
The editor provides you with an interface detailing all possibilities and allowing you to test your queries in
real-time.
Here's a video showing how to use the query editor:
# [Offers] List
Source: https://api-docs.jarvi.tech/en/api-reference/offers/get
GET /rest/v2/offers
Get all offers with their custom fields, files, and more.
You must use your **public api key** for this request. You need to cache the data on your end to comply with the daily
rate limits.
You can completely customize all information for each offer using Custom Fields. Click this link to understand how to read and write information in custom fields.
# [Profiles] List
Source: https://api-docs.jarvi.tech/en/api-reference/profiles/get
GET /rest/v2/profiles
Get all profiles with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
You must use your **private api key** for this request.
You can completely customize all information for each profile using Custom Fields. Click on this link to understand how to read and write information in custom fields.
# [Profiles] Create or update
Source: https://api-docs.jarvi.tech/en/api-reference/profiles/post
POST /rest/v2/profiles
Save a profile in Jarvi. It creates the profile if it does not exist, updates it if it does.
You must use your **private api key** for this request.
You can completely customize all information for each profile using Custom Fields. Click on this link to understand how to read and write information in custom fields.
# [Projects] List
Source: https://api-docs.jarvi.tech/en/api-reference/projects/get
GET /rest/v2/projects
Get all projects (recruitment, sales, etc.) with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
You must use your **private api key** for this request. You need to cache the data on your end to comply with the
daily rate limits.
You can completely customize all information for each project using Custom Fields. Click this link to understand how to read and write information in custom fields.
# [Projects] Create or update
Source: https://api-docs.jarvi.tech/en/api-reference/projects/post
POST /rest/v2/projects
Save a project in Jarvi. It creates the project if it does not exist, updates it if it does.
You must use your **private api key** for this request.
You can completely customize all information for each profile using Custom fields. Click on this link to understand how to read and write information in custom fields.
# Custom fields
Source: https://api-docs.jarvi.tech/en/custom-fields
Understanding custom fields will allow you to leverage the full potential of Jarvi and this API
Jarvi users can configure input fields available on:
* profiles (candidates or prospects)
* projects (recruitment or prospecting)
* companies
Each field is identified by a unique ID (UUID).
Follow this link to access Jarvi and get the list of all input fields available for the recruitment agency.
Through the API, you can retrieve information from these fields and modify them as well.
All you need is to know the unique id (UUID).
By clicking on the link above and then opening a field, you'll find its id at the end of the url.
For example, in `https://app.jarvi.tech/#/settings/fields/cdccbea3-debd-453d-8a30-691f48c5a9e5` the id is `cdccbea3-debd-453d-8a30-691f48c5a9e5`.
# Introduction
Source: https://api-docs.jarvi.tech/en/introduction
Welcome to the Jarvi API documentation
## Why use the Jarvi API?
This API allows you to do absolutely everything you want. This same API is used by the Jarvi application and its extension.
Here are the most common use cases:
Send applications from your website, forms, or other sources directly to Jarvi.
Get projects, job offers created in Jarvi and all associated information.
But remember, everything is possible.
## Terms of use
* The Jarvi API enforces limits based on the **cumulative execution time** of your requests, not on their number.
* These limits only apply **Monday to Friday, 07:00 to 18:00 UTC** Outside it — nights and weekends — no limit is enforced: that's the ideal window for your batch jobs.
* During business hours, your API usage is limited to **5 minutes** of cumulative execution time per hour.
* These limits are very generous and represent roughly **100 times the average usage** of a Jarvi user.
* When exceeded, the API returns a `429` error detailing your consumption, along with a `Retry-After` header.
* If you need a higher limit, contact our support team — we can adjust thresholds to match your usage.
* You commit to comply with data protection regulations, particularly GDPR, for all data read/written via the API.
## Quick start
Your API keys will give you access to the API
# MCP (AI Assistants)
Source: https://api-docs.jarvi.tech/en/mcp
Connect AI assistants like Claude Desktop or Cursor to your Jarvi data
## What is MCP?
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard that allows AI assistants to connect to external tools and data sources. Jarvi exposes an MCP server so you can use AI assistants to search, read, and update your Jarvi data directly.
## Compatible clients
Any MCP-compatible client can connect to Jarvi, including:
* [Claude Desktop](https://claude.ai/download)
* [Cursor](https://cursor.com)
* [Windsurf](https://codeium.com/windsurf)
* Any client supporting the **Streamable HTTP** transport
## Configuration
### Claude Desktop
Go to **Settings > Connectors > Add a custom connector**, enter a name (e.g., "Jarvi") and the URL:
```
https://mcp.jarvi.tech
```
Claude Desktop handles authentication automatically via OAuth2 — you'll be redirected to log in with your Jarvi account.
### Other clients
For clients that don't support OAuth2, use a private API key:
* **Transport**: Streamable HTTP
* **URL**: `https://mcp.jarvi.tech`
* **Authentication**: `x-api-key` header with your [private API key](./api-keys)
Your private key grants full access to your Jarvi data. Never share it publicly.
## Available tools
Once connected, the following tools are available for your AI assistant:
| Tool | Description |
| ------------------------- | -------------------------------------------------------------------------------- |
| `whoAmI` | Returns the identity of the logged-in user (name, email, workspace) |
| `getStatuses` | List available statuses for profiles, companies, or projects |
| `getCustomFields` | List custom fields defined for an entity type |
| `getHistorySubtypes` | List custom interaction subtypes (e.g., call categories) |
| `advancedSearch` | Search profiles, companies, or projects using natural language |
| `getEntityData` | Retrieve detailed data for profiles, companies, or projects by IDs |
| `getAgenda` | Retrieve upcoming events from connected calendars |
| `getNotifications` | Retrieve the user's recent notifications |
| `createProfile` | Create a new profile (talent and/or contact) |
| `createCompany` | Create a new company |
| `createProject` | Create a new project / recruitment assignment |
| `associateProfiles` | Link a profile to a project (talent or contact) or to a company |
| `createHistoryEntry` | Add a note or call log to a profile's history |
| `createTodo` | Create a reminder/to-do linked to a profile, company, or project |
| `sendMessage` | Send messages to one or more profiles (email, LinkedIn, WhatsApp, Telegram, SMS) |
| `getMessageTemplates` | List available message templates (personal and shared by colleagues) |
| `uploadFile` | Upload a file and attach it to an entity (profile, company, or project) |
| `cancelScheduledMessages` | Cancel one or more scheduled messages |
| `updateProfile` | Update a profile, add emails or phone numbers |
| `updateCompany` | Update a company, change status by name, add emails |
| `updateProject` | Update a project, change status by name, link a company |
| `fillJobOffer` | Generate or refine a job offer from project data |
| `getOutboxDiagnostic` | Diagnose the outbox: list pending scheduled messages with status and errors |
| `retryOutboxTasks` | Retry errored tasks by resetting their status |
| `queryGraphQL` | Execute GraphQL queries/mutations on authorized tables (your permissions apply) |
All operations run with **your permissions**. Row-level security applies — you can only access data your account has access to.
## Prompt Examples
Once connected, try asking your AI assistant:
* *"Who am I?"*
* *"Search for React developers in Paris"*
* *"Show me the details of project X"*
* *"Create a profile for John Doe, developer"*
* *"Position this candidate on project X"*
* *"Set the status of Acme company to Qualified"*
* *"What custom fields exist on profiles?"*
* *"Log a call with John Doe: discussed his availability"*
* *"Remind me to follow up with Acme next Monday"*
* *"Update John Doe's title to Senior Developer"*
## Limitations
* The same [usage limits](./introduction#terms-of-use) apply, counted on cumulative execution time. If you need more, contact our support team.
* Deletion operations are blocked — use soft delete (updating `deletedAt`) instead.
* Responses that are too large are automatically truncated. Use `limit` arguments in your queries.
* Destructive tools (mutations, campaign launches, message follow-ups) require confirmation from the AI assistant before execution.
# Searching
Source: https://api-docs.jarvi.tech/en/search
Filter profiles, companies and projects on indexed fields with the _search operator
## Why `_search`
Jarvi runs a full-text search engine over your data. Names, emails, phone numbers, job titles, skills and custom field values are indexed, and `_search` queries those indexes.
Filtering with `_ilike "%term%"` does something else entirely: it walks every record in your workspace and compares it to the pattern. On a production base, a single `_ilike` on a last name was measured at 2.4 seconds; the same search through the index answers in a few tens of milliseconds.
`_ilike`, `_like`, `_nilike` and `_nlike` are being retired from the public API on **31 July 2027**.
On the REST endpoints you have nothing to do — `_ilike` filters are already translated into an
indexed search on your behalf, and the response tells you which filter replaced yours. Through the
`/graphql` endpoint the query is yours to change, since it can address any table.
## Using the operator
A filter takes the name of a search target and a term:
```json theme={null}
{ "quickSearch": { "_search": "Dupont" } }
```
Combine several with `_and`, `_or` and `_not`, as you would with any other filter:
```json theme={null}
{
"_and": [
{ "globalSearch": { "_search": "javascript" } },
{ "isTalent": true },
{ "addedAt": { "_gte": "2026-01-01" } }
]
}
```
### Query syntax
The term itself is a query, not a plain string:
| Syntax | Matches |
| -------------------------------- | ------------------------------------------------------- |
| `developer designer` | either word |
| `developer AND designer` | both words |
| `developer AND (java OR kotlin)` | both, with either technology |
| `NOT intern` | records without the word |
| `"project manager"` | the exact phrase, words in that order |
| `java^10 python` | both words, java weighing ten times more in the ranking |
### Whole words, not fragments
Names are indexed word by word, from the beginning: `Dupo` finds *Dupont*, `upont` does not. The same applies to company and project names.
Emails and phone numbers are indexed differently and do match a fragment anywhere, so a phone number is found from its last digits, and an email from its domain.
## Filters, resource by resource
Every filter below is index-backed. Any other Hasura column or relation is still accepted, but it is neither optimised nor guaranteed.
### Profiles
| Filter | Operators | What it matches |
| -------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quickSearch` | `_search` | Searches name, email, phone and short id — the target for simple person or name lookups. |
| `globalSearch` | `_search` | Searches name, headline, summary, position titles, company names, skills, educations and custom field values — the target for skill, role or technology searches. |
| `emailPhoneAndNameSearch` | `_search` | Searches name, email, phone and short id — the target for simple person or name lookups. |
| `fullPositionTitleSkillsAndTagsSearch` | `_search` | Searches name, headline, summary, position titles, company names, skills, educations and custom field values — the target for skill, role or technology searches. |
| `headline` | `_search` | Headline of the profile: the tagline shown under the name on LinkedIn, or the summary line extracted from the CV for profiles that have no LinkedIn page. Editable by hand only when the profile has no LinkedIn identifier. |
| `currentPositions.global` | `_search` | Full-text over title, description |
| `currentPositions.global.title` | `_search` | Job title held at this position, coming from LinkedIn, from a parsed CV or entered by hand. This is the main field behind job title search. |
| `currentPositions.global.description` | `_search` | What the person wrote about this role on their profile or CV. Often empty on imported records. |
| `emails` | `_search` | Email address of the profile or the company it belongs to. Normalized on save: lowercased, and when the input is not a clean address the first valid one found inside it is kept, the row being dropped if there is none. |
| `phones` | `_search` | Phone number search. Only the digits are compared, so a number matches whatever spacing, separators or country prefix it was typed with, and a suffix matches too. |
| `profiles.shortId` | `_search` | Short readable reference for the profile — what users type in quick search and what appears on generated documents. Assigned once at creation and never changes. |
| `historyEntries` | `_search` | Full-text over subject, message |
| `historyEntries.subject` | `_search` | Subject of the interaction: the email subject, the meeting title, or the job offer title. Empty for channels that carry no subject, such as LinkedIn messages, WhatsApp and SMS. |
| `historyEntries.message` | `_search` | Content of the interaction: the email body, the text of the LinkedIn, WhatsApp, SMS or Telegram message, or what the recruiter typed for a manual note. |
| `historyEntries.type` | `_eq`, `_in`, `_is_null` | What kind of interaction this entry records: emails, LinkedIn messages and InMails, connection invitations, SMS, WhatsApp, Telegram, phone calls, scheduled meetings, job applications, or a free note. |
| `historyEntries.subTypeId` | `_eq`, `_in`, `_is_null` | Optional sub-category of the interaction, defined by the group itself — naming its own kinds of notes or calls, for instance. Each sub-category belongs to a single parent type. |
| `historyEntries.occurredAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Single date used to place the interaction on the timeline: the meeting start for a scheduled event, the creation date otherwise. Maintained by a trigger. |
| `historyEntries.userId` | `_eq`, `_in`, `_is_null` | Jarvi user this entry belongs to. Determines the group the entry is attached to, and is used as its sender by default. |
| `historyEntries.hasFiles` | `true`, `false` | The profile has at least one interaction carrying an attached file. |
| `historyEntries.hasRecording` | `true`, `false` | The profile has at least one interaction with a meeting recording. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `location` | `_near` | Geographic search. Use range 0 for an exact city or region match, or a radius in meters. |
| `hasEmail` | `true`, `false` | The profile has at least one email address. |
| `hasEmailType` | `_eq`, `_in`, `_is_null` | The profile has an email address of this type. Takes the same values as the type of an email address. |
| `hasPhone` | `true`, `false` | The profile has at least one phone number. |
| `hasValidPhone` | `true`, `false` | The profile has at least one phone number not marked as invalid. |
| `hasLocation` | `true`, `false` | The profile has at least one location. |
| `hasPhoneType` | `_eq`, `_in`, `_is_null` | The profile has a phone number of this type. Takes the same values as the type of a phone number. |
| `hasWhatsApp` | `true`, `false` | The profile has a phone number reachable on WhatsApp. |
| `isTalent` | `true`, `false` | The profile is a talent — someone you recruit, followed in the ATS. A profile can be a talent and a contact at once. |
| `isContact` | `true`, `false` | The profile is a contact — a client or a hiring manager, followed in the CRM. A profile can be a contact and a talent at once. |
| `hasLinkedin` | `true`, `false` | The profile is matched to a LinkedIn account. |
| `hasResume` | `true`, `false` | The profile has at least one CV attached. |
| `isConnectedOnLinkedinWithMe` | `true`, `false` | The calling user is connected with this profile on LinkedIn, as recorded in the profile's history. |
| `hasReplied` | `true`, `false` | The profile has answered at least once, on any channel. |
| `hasScheduledMessage` | `true`, `false` | A message or a connection invitation is still waiting to be sent to this profile. |
| `hasUnreadMessagesNotifications` | `true`, `false` | The profile has an unread message notification. |
| `firstPositionStartDate` | `_eq`, `_gte`, `_lte`, `_is_null` | Start date of the earliest position on the profile — the usual way to reason about years of experience. |
| `lastPositionStartDate` | `_eq`, `_gte`, `_lte`, `_is_null` | Start date of the current position, that is the one with no end date. |
| `currentPositions.companyId` | `_eq`, `_in`, `_is_null` | The profile currently works at this company. Takes the identifier of the LinkedIn company page, not a Jarvi company id. |
| `projectId` | `_eq`, `_in`, `_is_null` | The profile is associated with this project. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the profile. A parent status also matches profiles carrying any of its children, and the search is narrowed by the project or entity given in the context. Pass "other" for profiles whose status has no parent. |
| `hasProject` | `true`, `false` | The profile is associated with at least one project. |
| `projectCount` | `_eq`, `_gte`, `_lte`, `_is_null` | How many distinct projects the profile is associated with. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `isInCrm` | `true`, `false` | The profile is followed in the CRM, meaning it is a contact. Same as isContact. |
| `isInAts` | `true`, `false` | The profile is followed in the ATS, meaning it is a talent. Same as isTalent. |
| `hasAttachedFiles` | `true`, `false` | The profile has at least one attached file. |
| `isOpenToNewOpportunities` | `true`, `false` | The profile is flagged as open to new opportunities. |
| `isHiring` | `true`, `false` | The profile is flagged as hiring. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the profile, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Technical timestamp of the row creation. For the date the profile entered the base, use addedAt (added\_at) instead. |
| `addedAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the profile entered the group's base — what recruiters read as "added on". Set automatically when the profile is created or restored, unless the caller provides its own value, which is what imports do to keep the original date. |
| `externalId` | `_eq`, `_in` | Identifier of the profile in the client's own system — their ATS, their CRM, or whatever calls the public API. Never a Jarvi identifier. Unique per group, and used to recognise a profile that is sent again. |
| `publicIdentifier` | `_eq`, `_in` | LinkedIn public slug, the part after linkedin.com/in/. Unique per group. The person can change it on LinkedIn, which makes it the least reliable of the LinkedIn identifiers. |
| `linkedinId` | `_eq`, `_in` | Numeric LinkedIn identifier of the person. Unique per group. Not to be confused with memberId (member\_id), which holds the obfuscated "ACoAA…" identifier, nor with publicIdentifier (public\_identifier), which holds the URL slug. |
| `primaryLocale` | `_eq`, `_in`, `_is_null` | Main language of the profile itself — the language its LinkedIn page or CV is written in. Different from the profiles\_locales table, which lists the languages the person speaks and how well. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
### Companies
| Filter | Operators | What it matches |
| ---------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `companySearch` | `_search` | Searches company name and description, LinkedIn tagline and description, legal activity and NAF code, and custom fields — the target for keyword or industry searches. |
| `companyQuickSearch` | `_search` | Searches company name, email and phone — for name lookups only. |
| `companies.global` | `_search` | Company name and description. |
| `location` | `_near` | Geographic search. Use range 0 for an exact city or region match, or a radius in meters. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `hasLinkedinCompanyId` | `true`, `false` | The company is matched to a LinkedIn company page. |
| `hasContacts` | `true`, `false` | The company has at least one profile associated as a contact. |
| `hasProject` | `true`, `false` | The company has at least one project. |
| `isParentCompany` | `true`, `false` | The company has at least one subsidiary. |
| `isSubsidiary` | `true`, `false` | The company has a parent company. |
| `isProtected` | `true`, `false` | The company is under protection, meaning its protection date is still in the future. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the company. A parent status also matches companies carrying any of its children. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the company, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the company record was created. Data imports replace it with the creation date coming from the source ATS or CRM. |
| `externalId` | `_eq`, `_in` | Identifier of the company in the client's own system. Unique per group, and used as the matching key by imports and by the public API. |
| `linkedinCompanyId` | `_eq`, `_in` | Link to the LinkedIn page data of this company. That data is shared across all groups, and a given LinkedIn page can be attached to at most one company per group. |
| `parentId` | `_eq`, `_in`, `_is_null` | Parent company when this record is a subsidiary. Used as a single level: searching on a parent can include its direct subsidiaries, but not their own. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
### Projects
| Filter | Operators | What it matches |
| --------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectNameSearch` | `_search` | Searches project name, its company name and its reference id — the target for project lookups. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `hasJobBoardEnabled` | `true`, `false` | The project is published on at least one job board. |
| `hasLinkedinId` | `true`, `false` | The project is matched to a LinkedIn job posting. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the project. A parent status also matches projects carrying any of its children. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the project, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the project was created. Data imports replace it with the creation date coming from the source ATS or CRM. |
| `externalId` | `_eq`, `_in` | Identifier of the project in the client's own system. Unique per group, and used as the matching key by imports and by the public API. |
| `referenceId` | `_eq`, `_in` | The project reference id is used to map external job applications on website or jobboard to the project. |
| `companyId` | `_eq`, `_in`, `_is_null` | Client company the project is for. Optional — a project can exist without a company, and deleting the company clears the link rather than the project. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
These lists are generated from the search engine itself, so they are always what the API accepts.
The same definitions are served as JSON Schema by [GET
/openapi](https://functions.prod.jarvi.tech/v1/public-api/openapi), under `ProfilesFilters`,
`CompaniesFilters` and `ProjectsFilters`.
## Sorting
The `orderBy` parameter of each REST endpoint pairs one field with a direction:
```
?orderBy={"addedAt":"desc"}
```
The fields a resource accepts are listed in the parameter's own documentation, on the endpoint page. Sorting on anything else falls back to the default order rather than failing.
## Searching through GraphQL
The `search` query returns identifiers and a total, which you then load in a second call. Fetching in two steps is what keeps a search fast, whatever the size of the records:
```graphql theme={null}
query FindDevelopers {
search(
resource: "profiles"
filters: { globalSearch: { _search: "javascript AND react" }, isTalent: true }
sort: { field: "addedAt", order: "DESC" }
limit: 50
) {
ids
total
}
}
```
```graphql theme={null}
query LoadThem($ids: [uuid!]!) {
profiles(where: { id: { _in: $ids } }) {
id
firstName
lastName
}
}
```
`resource` is `profiles`, `companies` or `projects`. `filters` takes the same object as the REST `where` parameter.
## Moving off `_ilike`
| Instead of | Write |
| --------------------------------------------------------- | --------------------------------------------- |
| `{"lastName": {"_ilike": "%Dupont%"}}` | `{"quickSearch": {"_search": "Dupont"}}` |
| `{"emailAddresses": {"email": {"_ilike": "%@acme.com"}}}` | `{"emails": {"_search": "acme.com"}}` |
| `{"headline": {"_ilike": "%developer%"}}` | `{"globalSearch": {"_search": "developer"}}` |
| `{"name": {"_ilike": "%Acme%"}}` (companies) | `{"companyQuickSearch": {"_search": "Acme"}}` |
| `{"externalId": {"_ilike": "ABC-123"}}` | `{"externalId": {"_eq": "ABC-123"}}` |
An identifier or a URL is never a search: match it with `_eq`, which uses its own index.
Something missing, or a filter you expected and did not find? Write to us — the list above is
driven by the engine, so adding a filter to it is a small change.
# Submit applications
Source: https://api-docs.jarvi.tech/en/use-cases/applicants
This API will allow you to send candidates from your website, form, or any other source directly to Jarvi.
Here's how to proceed:
If you don't already have your API keys, follow the steps in the section
Get my API keys
Check out the detailed documentation of the API post applicants. It will also be useful to understand the concept of Custom fields
Don't worry, you're not the first one, and you'll probably find examples of integrations below that are similar to what you want.
Here's an example of a Wordpress integration using the QuForm plugin for forms.
```php theme={null}
add_action('quform_post_process_{ID_FORM}', function (array $result, Quform_Form $form){
$file_content = file_get_contents( $this->attachments[ 0 ] );
$file_base_64_content = base64_encode( $file_content );
$data = [
'firstName' => $form->getValue('quform_{ID_FORM}_5'),
'lastName' => $form->getValue('quform_{ID_FORM}_7'),
'fullName' => $form->getValue('quform_{ID_FORM}_6') . ' ' . $form->getValue('quform_{ID_FORM}_7'),
'cb9d4872-bfaa-4c57-b1af-91c008883386' => $form->getValue('quform_{ID_FORM}_10'), // Poste
'17fe3ebf-3641-4a94-ac9f-f11a6d429107' => $form->getValue('quform_{ID_FORM}_31'), // Exp SaaS
'6b751e8d-3947-43f9-af52-1db532210c6d' => $form->getValue('quform_{ID_FORM}_12'), // 1e année CDI
'region' => $form->getValue('quform_{ID_FORM}_15'),
'linkedinUrl' => $form->getValue('quform_{ID_FORM}_17'),
'phoneNumbers' => $form->getValue('quform_{ID_FORM}_20'),
'emailAddresses' => $form->getValue('quform_{ID_FORM}_22'),
'resumesFiles' => [
[
'data' => $file_base_64_content,
'fileName' => $this->attachments[0]->file_name,
],
],
];
wp_remote_post('https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants', [
'body' => json_encode($data),
'headers' => array(
'Content-Type' => 'application/json',
'X-Api-Key' => '{JARVI_PRIVATE_API_KEY}'
),
]);
return $result;
}, 10, 2);
```
Here's an example of integration in WebFlow. This integration also works for a classic website.
```javascript theme={null}
document.querySelector('#form-submit-button').addEventListener('click', function() {
var formElement = document.querySelector('.inscription-candidat-form');
var formData = new FormData(formElement);
var formDataAsObject = {}
for (var [key, value] of formData.entries()) {
console.log(key, value);
formDataAsObject[key] = value
}
if(!formDataAsObject.linkedinUrl) console.error("linkedinUrl is mandatory");
const data = {
"referenceId":"PROJECT-REFERENCE-ID",
...formDataAsObject
}
console.log("data to send",data)
fetch('https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants', {
method: 'POST',
mode:'cors',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'JARVI_PUBLIC_API_KEY'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => console.log("data sent",data))
.catch((error) => {
console.error('Error:', error);
});
});
```
Here's an example of integration in WebFlow. This integration also works for a classic website.
```javascript theme={null}
// Fill those constants with your own values
const FORM_SELECTOR = "#candidature-form";
const SUBMIT_BUTTON_SELECTOR = "#form-submit-button";
const RESUME_INPUT_NAME = "resumesFiles";
const PROJECT_REFERENCE_ID = "PROJECT-REFERENCE-ID";
const JARVI_PUBLIC_API_KEY = "YOUR JARVI PUBLIC API KEY";
// Helper function to read file as base64
const readFileAsBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => resolve(event.target.result);
reader.onerror = (error) => reject(error);
reader.readAsDataURL(file);
});
};
// Helper function to validate form data
const validateFormData = (formData) => {
if (!formData.linkedinUrl) {
throw new Error("LinkedIn URL is mandatory");
}
return true;
};
// Main form submission handler
document.querySelector(SUBMIT_BUTTON_SELECTOR).addEventListener("click", async function (event) {
event.preventDefault(); // Prevent default form submission
try {
const formElement = document.querySelector(FORM_SELECTOR);
if (!formElement) {
throw new Error("Form element not found");
}
// Convert FormData to object
const formData = new FormData(formElement);
const formDataAsObject = Object.fromEntries(formData.entries());
// Validate form data
validateFormData(formDataAsObject);
// Get and validate file
const fileInput = formElement.querySelector(`input[name="${RESUME_INPUT_NAME}"]`);
if (!fileInput) {
throw new Error("Resume input field not found");
}
const file = fileInput.files[0];
if (!file) {
throw new Error("No file uploaded");
}
// Read file and prepare data
const base64Data = await readFileAsBase64(file);
const resumesFiles = [
{
fileName: file.name,
data: base64Data,
},
];
const data = {
referenceId: "PROJECT-REFERENCE-ID", // Update with your project reference ID
...formDataAsObject,
resumesFiles,
};
console.log("Sending data:", data);
// Send data to API
const response = await fetch("https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
"X-Api-Key": JARVI_PUBLIC_API_KEY,
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
console.log("Data sent successfully:", responseData);
// Optional: Show success message to user
alert("Application submitted successfully!");
} catch (error) {
console.error("Error:", error);
alert(`Error: ${error.message}`);
}
});
```
# Retrieve projects
Source: https://api-docs.jarvi.tech/en/use-cases/projects
This API allows you to retrieve all recruitment projects as well as prospecting projects and their associated information. The goal is often to display them on your website.
Here's how to proceed:
If you don't already have your API keys, follow the steps in the Get my API keys section
Check out the detailed documentation of the GET projects API. It will
also be helpful to understand the concept of Custom fields
Now that you've retrieved the projects, you might also want to send applications to Jarvi:
Use case: sending applications to Jarvi
# Obtener mis claves de API
Source: https://api-docs.jarvi.tech/es/api-keys
Tus claves de API te permitirán acceder a la API
Ve a tus preferencias, luego a API. Haz clic en el botón para generar las claves de API como se muestra a continuación.
Obtendrás 2 claves:
* Una **clave pública**, que solo permite crear nuevos perfiles y nuevas candidaturas.
* Una **clave privada**, que permite hacer todo. Esta clave solo se mostrará una vez, en el momento de su creación. Asegúrate de guardarla en un lugar seguro.
La clave privada no debe ser divulgada, ni estar presente en el código javascript, accesible al usuario.
# [Candidatura] Crear o actualizar
Source: https://api-docs.jarvi.tech/es/api-reference/applicants/post
POST /rest/v2/applicants
Save a new job application in Jarvi. It creates the profile if it does not exist, upload the file, link the profile to the recruitment project,...
Puedes usar tu **clave de API pública** para esta solicitud. Esto te permite hacerlo directamente en el código
javascript del sitio.
El tamaño máximo de la solicitud es de 4.5MB. Te aconsejamos limitar el tamaño de tu campo de carga de CV a un tamaño
inferior.
Puedes guardar todo en el perfil gracias a los Campos personalizados. Haz clic en este enlace para entender cómo leer y escribir información en los campos personalizados.
# [Empresas] Listar
Source: https://api-docs.jarvi.tech/es/api-reference/companies/get
GET /rest/v2/companies
Get all companies (recruitment, sales, etc.) with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Debes utilizar tu **clave de api privada** para esta solicitud.
Hay cambios previstos en esta API para el verano de 2025. Por lo tanto, es posible que sean necesarias modificaciones
por tu parte tras estas evoluciones.
Puedes personalizar completamente la información de cada perfil gracias a los Campos personalizados. Haz clic en este enlace para entender cómo leer y escribir información en los campos personalizados.
# [Empresa] Crear o actualizar
Source: https://api-docs.jarvi.tech/es/api-reference/companies/post
POST /rest/v2/companies
Save a company in Jarvi. It creates the company if it does not exist, updates it if it does.
Debes utilizar tu **clave de api privada** para esta solicitud.
Puedes personalizar completamente la información de cada perfil gracias a los Campos personalizados. Haz clic en este enlace para entender cómo leer y escribir información en los campos personalizados.
# GraphQL
Source: https://api-docs.jarvi.tech/es/api-reference/graphql
POST /graphql
¡La API completa para hacer todo lo demás!
Esta API te permite hacer absolutamente todo lo que desees. Puedes personalizar tus consultas para leer/escribir lo que necesites.
No garantizamos que la información disponible en esta API no cambie sin previo aviso. Te aconsejamos no utilizar esta
API para usos críticos sin consultarnos previamente.
El editor te proporciona una interfaz que detalla todas las posibilidades y te permite probar tus consultas en
tiempo real.
Aquí puedes ver cómo utilizar el editor de consultas en video:
# [Ofertas] Listar
Source: https://api-docs.jarvi.tech/es/api-reference/offers/get
GET /rest/v2/offers
Get all offers with their custom fields, files, and more.
Debes usar tu **clave de api pública** para esta solicitud. Debes almacenar en caché los datos de tu lado para
respetar los límites de número de llamadas por día.
Puedes personalizar completamente la información de cada proyecto gracias a los Campos personalizados. Haz clic en este enlace para entender cómo leer y escribir información en los campos personalizados.
# [Perfiles] Listar
Source: https://api-docs.jarvi.tech/es/api-reference/profiles/get
GET /rest/v2/profiles
Get all profiles with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Debes usar tu **clave de api privada** para esta solicitud.
Puedes personalizar completamente la información de cada perfil gracias a los Campos personalizados. Haz clic en este enlace para entender cómo leer y escribir información en los campos personalizados.
# [Perfiles] Crear o actualizar
Source: https://api-docs.jarvi.tech/es/api-reference/profiles/post
POST /rest/v2/profiles
Save a profile in Jarvi. It creates the profile if it does not exist, updates it if it does.
Debes usar tu **clave de api privada** para esta solicitud.
Puedes personalizar completamente la información de cada perfil gracias a los Campos personalizados. Haz clic en este enlace para entender cómo leer y escribir información en los campos personalizados.
# [Proyectos] Listar
Source: https://api-docs.jarvi.tech/es/api-reference/projects/get
GET /rest/v2/projects
Get all projects (recruitment, sales, etc.) with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Debes usar tu **clave de api privada** para esta solicitud. Debes almacenar en caché los datos de tu lado para
respetar los límites de número de llamadas por día.
Puedes personalizar completamente la información de cada proyecto gracias a los Campos personalizados. Haz clic en este enlace para entender cómo leer y escribir información en los campos personalizados.
# [Proyectos] Crear o actualizar
Source: https://api-docs.jarvi.tech/es/api-reference/projects/post
POST /rest/v2/projects
Save a project in Jarvi. It creates the project if it does not exist, updates it if it does.
Debes utilizar tu **clave de api privada** para esta solicitud.
Puedes personalizar completamente la información de cada perfil gracias a los Campos personalizados. Haz clic en este enlace para entender cómo leer y escribir información en los campos personalizados.
# Campos personalizados
Source: https://api-docs.jarvi.tech/es/custom-fields
Entender el concepto de campos personalizados te permitirá aprovechar todo el potencial de Jarvi y esta API
Los usuarios de Jarvi pueden configurar los campos de entrada disponibles en los
* perfiles (candidatos o prospectos)
* proyectos (de reclutamiento o prospección)
* empresas
Cada campo se identifica por un ID único (UUID).
Sigue este enlace para acceder a Jarvi y obtener la lista de todos los campos de entrada disponibles para la agencia
de reclutamiento.
Gracias a la API, puedes recuperar la información de estos campos y también modificarla.
Para ello, solo necesitas conocer su id único (UUID).
Haciendo clic en el enlace de arriba y luego abriendo un campo, encontrarás su id al final de la url.
Por ejemplo, en `https://app.jarvi.tech/#/settings/fields/cdccbea3-debd-453d-8a30-691f48c5a9e5` el id es `cdccbea3-debd-453d-8a30-691f48c5a9e5`.
# Introducción
Source: https://api-docs.jarvi.tech/es/introduction
Bienvenido a la documentación de la API de Jarvi
## ¿Por qué usar la API de Jarvi?
Esta API te permite hacer absolutamente todo lo que desees. La misma API es utilizada por la aplicación Jarvi y su extensión.
Aquí están los casos de uso más comunes:
Envía las candidaturas provenientes de tu sitio web, formularios u otros directamente a Jarvi.
Obtén los proyectos, las ofertas de empleo creadas en Jarvi y toda la información asociada.
Pero recuerda, todo es posible.
## Condiciones de uso
* La API de Jarvi aplica límites basados en el **tiempo de ejecución acumulado** de tus solicitudes, no en su número.
* Estos límites solo se aplican **de lunes a viernes, de 07:00 a 18:00 UTC** (06:00–17:00 UTC en horario de verano). Fuera de ella — noches y fines de semana — no se aplica ningún límite: es el momento ideal para tus procesos por lotes.
* Durante el horario laboral, tu uso está limitado a **5 minutos** de tiempo de ejecución acumulado por hora.
* Estos límites son muy generosos y representan aproximadamente **100 veces el uso promedio** de un usuario de Jarvi.
* Si se superan, la API devuelve un error `429` detallando tu consumo, junto con una cabecera `Retry-After`.
* Si necesitas un límite superior, contacta con nuestro soporte — podemos ajustar los umbrales a tu uso.
* Te comprometes a respetar la normativa relativa a la protección de datos personales, en particular el RGPD, para todos los datos leídos/escritos a través de la API.
## Inicio rápido
Tus claves de API te permitirán acceder a la API
# MCP (Asistentes de IA)
Source: https://api-docs.jarvi.tech/es/mcp
Conecta asistentes de IA como Claude Desktop o Cursor a tus datos de Jarvi
## ¿Qué es el MCP?
El [Model Context Protocol (MCP)](https://modelcontextprotocol.io) es un estándar abierto que permite a los asistentes de IA conectarse a herramientas y fuentes de datos externas. Jarvi expone un servidor MCP para que puedas usar asistentes de IA para buscar, leer y actualizar tus datos de Jarvi directamente.
## Clientes compatibles
Cualquier cliente compatible con MCP puede conectarse a Jarvi, incluyendo:
* [Claude Desktop](https://claude.ai/download)
* [Cursor](https://cursor.com)
* [Windsurf](https://codeium.com/windsurf)
* Cualquier cliente que soporte el transporte **Streamable HTTP**
## Configuración
### Claude Desktop
Ve a **Ajustes > Conectores > Añadir un conector personalizado**, introduce un nombre (ej: "Jarvi") y la URL:
```
https://mcp.jarvi.tech
```
Claude Desktop gestiona la autenticación automáticamente mediante OAuth2 — serás redirigido para iniciar sesión con tu cuenta de Jarvi.
### Otros clientes
Para clientes que no soportan OAuth2, utiliza una clave privada de API:
* **Transporte**: Streamable HTTP
* **URL**: `https://mcp.jarvi.tech`
* **Autenticación**: encabezado `x-api-key` con tu [clave privada de API](./api-keys)
Tu clave privada otorga acceso completo a tus datos de Jarvi. ¡Nunca la compartas públicamente!
## Herramientas disponibles
Una vez conectado, las siguientes herramientas están disponibles para tu asistente de IA:
| Herramienta | Descripción |
| ------------------------- | -------------------------------------------------------------------------------------------------- |
| `whoAmI` | Devuelve la identidad del usuario conectado (nombre, email, workspace) |
| `getStatuses` | Listar los estados disponibles para perfiles, empresas o proyectos |
| `getCustomFields` | Listar los campos personalizados definidos para un tipo de entidad |
| `getHistorySubtypes` | Listar los subtipos personalizados de interacciones (ej: categorías de llamadas) |
| `advancedSearch` | Buscar perfiles, empresas o proyectos en lenguaje natural |
| `getEntityData` | Obtener los datos detallados de perfiles, empresas o proyectos por IDs |
| `getAgenda` | Obtener los eventos próximos de las agendas conectadas |
| `getNotifications` | Obtener las notificaciones recientes del usuario |
| `createProfile` | Crear un nuevo perfil (talento y/o contacto) |
| `createCompany` | Crear una nueva empresa |
| `createProject` | Crear un nuevo proyecto / misión de reclutamiento |
| `associateProfiles` | Vincular un perfil a un proyecto (talento o contacto) o a una empresa |
| `createHistoryEntry` | Añadir una nota o un registro de llamada al historial de un perfil |
| `createTodo` | Crear un recordatorio/tarea vinculado a un perfil, empresa o proyecto |
| `sendMessage` | Enviar mensajes a uno o varios perfiles (email, LinkedIn, WhatsApp, Telegram, SMS) |
| `getMessageTemplates` | Listar las plantillas de mensajes disponibles (personales y compartidas por compañeros) |
| `uploadFile` | Subir un archivo y vincularlo a una entidad (perfil, empresa o proyecto) |
| `cancelScheduledMessages` | Cancelar uno o varios mensajes programados |
| `updateProfile` | Actualizar un perfil, añadir emails o números de teléfono |
| `updateCompany` | Actualizar una empresa, cambiar el estado por nombre, añadir emails |
| `updateProject` | Actualizar un proyecto, cambiar el estado por nombre, vincular una empresa |
| `fillJobOffer` | Generar o refinar una oferta de empleo a partir de los datos del proyecto |
| `getOutboxDiagnostic` | Diagnosticar la bandeja de salida: listar los mensajes programados pendientes con estado y errores |
| `retryOutboxTasks` | Reintentar las tareas con errores restableciendo su estado |
| `queryGraphQL` | Ejecutar consultas/mutaciones GraphQL en las tablas autorizadas (se aplican tus permisos) |
Todas las operaciones se ejecutan con **tus permisos**. La seguridad a nivel de filas se aplica — solo puedes acceder a los datos a los que tu cuenta tiene acceso.
## Ejemplos de prompts
Una vez conectado, intenta pedirle a tu asistente de IA:
* *"¿Quién soy?"*
* *"Busca desarrolladores React en París"*
* *"Muéstrame los detalles del proyecto X"*
* *"Crea un perfil para Jean Dupont, desarrollador"*
* *"Posiciona este candidato en el proyecto X"*
* *"Cambia el estado de la empresa Acme a Cualificado"*
* *"¿Qué campos personalizados existen en los perfiles?"*
* *"Registra una llamada con Jean Dupont: hablamos sobre su disponibilidad"*
* *"Recuérdame que contacte a Acme el próximo lunes"*
* *"Actualiza el título de Jean Dupont a Desarrollador Senior"*
## Limitaciones
* Se aplican los mismos [límites de uso](./introduction#condiciones-de-uso), contabilizados sobre el tiempo de ejecución acumulado. Si necesitas más, contacta con nuestro soporte.
* Las operaciones de eliminación están bloqueadas — usa eliminación suave (actualización de `deletedAt`) en su lugar.
* Las respuestas demasiado grandes se truncan automáticamente. Usa argumentos `limit` en tus consultas.
* Las herramientas destructivas (mutaciones, lanzamiento de campañas, reenvío de mensajes) requieren confirmación del asistente de IA antes de ejecutarse.
# Búsqueda
Source: https://api-docs.jarvi.tech/es/search
Filtrar perfiles, empresas y proyectos por campos indexados con el operador _search
## Por qué `_search`
Jarvi ejecuta un motor de búsqueda de texto completo sobre tus datos. Los nombres, correos, números de teléfono, puestos, competencias y valores de campos personalizados están indexados, y `_search` consulta esos índices.
Filtrar con `_ilike "%término%"` hace algo muy distinto: PostgreSQL recorre cada registro de tu espacio y lo compara con el patrón. En una base de producción, un solo `_ilike` sobre un apellido se midió en 2,4 segundos; la misma búsqueda a través del índice responde en unas decenas de milisegundos.
`_ilike`, `_like`, `_nilike` y `_nlike` se retirarán de la API pública el **31 de julio de 2027**.
En los endpoints REST no tienes que hacer nada: los filtros `_ilike` ya se traducen a una búsqueda
indexada por ti, y la respuesta indica qué filtro sustituyó al tuyo. A través del endpoint
`/graphql` la consulta es tuya y puede apuntar a cualquier tabla, así que te toca cambiarla.
## Usar el operador
Un filtro toma el nombre de un destino de búsqueda y un término:
```json theme={null}
{ "quickSearch": { "_search": "Dupont" } }
```
Combina varios con `_and`, `_or` y `_not`, como con cualquier otro filtro:
```json theme={null}
{
"_and": [
{ "globalSearch": { "_search": "javascript" } },
{ "isTalent": true },
{ "addedAt": { "_gte": "2026-01-01" } }
]
}
```
### Sintaxis de consulta
El término es en sí mismo una consulta, no una simple cadena:
| Sintaxis | Coincide con |
| -------------------------------- | ------------------------------------------------------------- |
| `developer designer` | cualquiera de las dos palabras |
| `developer AND designer` | ambas palabras |
| `developer AND (java OR kotlin)` | ambas, con cualquiera de las dos tecnologías |
| `NOT intern` | los registros sin esa palabra |
| `"jefe de proyecto"` | la frase exacta, en ese orden |
| `java^10 python` | ambas palabras, con java pesando diez veces más en el ranking |
### Palabras completas, no fragmentos
Los nombres se indexan palabra por palabra, desde el principio: `Dupo` encuentra *Dupont*, `upont` no. Lo mismo ocurre con los nombres de empresas y proyectos.
Los correos y los teléfonos se indexan de otra forma y sí aceptan un fragmento en cualquier posición: un número se encuentra por sus últimas cifras, un correo por su dominio.
## Los filtros, recurso por recurso
Todos los filtros siguientes se apoyan en un índice. Las demás columnas y relaciones de Hasura se siguen aceptando, pero no están optimizadas ni garantizadas.
### Profiles
| Filter | Operators | What it matches |
| -------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quickSearch` | `_search` | Searches name, email, phone and short id — the target for simple person or name lookups. |
| `globalSearch` | `_search` | Searches name, headline, summary, position titles, company names, skills, educations and custom field values — the target for skill, role or technology searches. |
| `emailPhoneAndNameSearch` | `_search` | Searches name, email, phone and short id — the target for simple person or name lookups. |
| `fullPositionTitleSkillsAndTagsSearch` | `_search` | Searches name, headline, summary, position titles, company names, skills, educations and custom field values — the target for skill, role or technology searches. |
| `headline` | `_search` | Headline of the profile: the tagline shown under the name on LinkedIn, or the summary line extracted from the CV for profiles that have no LinkedIn page. Editable by hand only when the profile has no LinkedIn identifier. |
| `currentPositions.global` | `_search` | Full-text over title, description |
| `currentPositions.global.title` | `_search` | Job title held at this position, coming from LinkedIn, from a parsed CV or entered by hand. This is the main field behind job title search. |
| `currentPositions.global.description` | `_search` | What the person wrote about this role on their profile or CV. Often empty on imported records. |
| `emails` | `_search` | Email address of the profile or the company it belongs to. Normalized on save: lowercased, and when the input is not a clean address the first valid one found inside it is kept, the row being dropped if there is none. |
| `phones` | `_search` | Phone number search. Only the digits are compared, so a number matches whatever spacing, separators or country prefix it was typed with, and a suffix matches too. |
| `profiles.shortId` | `_search` | Short readable reference for the profile — what users type in quick search and what appears on generated documents. Assigned once at creation and never changes. |
| `historyEntries` | `_search` | Full-text over subject, message |
| `historyEntries.subject` | `_search` | Subject of the interaction: the email subject, the meeting title, or the job offer title. Empty for channels that carry no subject, such as LinkedIn messages, WhatsApp and SMS. |
| `historyEntries.message` | `_search` | Content of the interaction: the email body, the text of the LinkedIn, WhatsApp, SMS or Telegram message, or what the recruiter typed for a manual note. |
| `historyEntries.type` | `_eq`, `_in`, `_is_null` | What kind of interaction this entry records: emails, LinkedIn messages and InMails, connection invitations, SMS, WhatsApp, Telegram, phone calls, scheduled meetings, job applications, or a free note. |
| `historyEntries.subTypeId` | `_eq`, `_in`, `_is_null` | Optional sub-category of the interaction, defined by the group itself — naming its own kinds of notes or calls, for instance. Each sub-category belongs to a single parent type. |
| `historyEntries.occurredAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Single date used to place the interaction on the timeline: the meeting start for a scheduled event, the creation date otherwise. Maintained by a trigger. |
| `historyEntries.userId` | `_eq`, `_in`, `_is_null` | Jarvi user this entry belongs to. Determines the group the entry is attached to, and is used as its sender by default. |
| `historyEntries.hasFiles` | `true`, `false` | The profile has at least one interaction carrying an attached file. |
| `historyEntries.hasRecording` | `true`, `false` | The profile has at least one interaction with a meeting recording. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `location` | `_near` | Geographic search. Use range 0 for an exact city or region match, or a radius in meters. |
| `hasEmail` | `true`, `false` | The profile has at least one email address. |
| `hasEmailType` | `_eq`, `_in`, `_is_null` | The profile has an email address of this type. Takes the same values as the type of an email address. |
| `hasPhone` | `true`, `false` | The profile has at least one phone number. |
| `hasValidPhone` | `true`, `false` | The profile has at least one phone number not marked as invalid. |
| `hasLocation` | `true`, `false` | The profile has at least one location. |
| `hasPhoneType` | `_eq`, `_in`, `_is_null` | The profile has a phone number of this type. Takes the same values as the type of a phone number. |
| `hasWhatsApp` | `true`, `false` | The profile has a phone number reachable on WhatsApp. |
| `isTalent` | `true`, `false` | The profile is a talent — someone you recruit, followed in the ATS. A profile can be a talent and a contact at once. |
| `isContact` | `true`, `false` | The profile is a contact — a client or a hiring manager, followed in the CRM. A profile can be a contact and a talent at once. |
| `hasLinkedin` | `true`, `false` | The profile is matched to a LinkedIn account. |
| `hasResume` | `true`, `false` | The profile has at least one CV attached. |
| `isConnectedOnLinkedinWithMe` | `true`, `false` | The calling user is connected with this profile on LinkedIn, as recorded in the profile's history. |
| `hasReplied` | `true`, `false` | The profile has answered at least once, on any channel. |
| `hasScheduledMessage` | `true`, `false` | A message or a connection invitation is still waiting to be sent to this profile. |
| `hasUnreadMessagesNotifications` | `true`, `false` | The profile has an unread message notification. |
| `firstPositionStartDate` | `_eq`, `_gte`, `_lte`, `_is_null` | Start date of the earliest position on the profile — the usual way to reason about years of experience. |
| `lastPositionStartDate` | `_eq`, `_gte`, `_lte`, `_is_null` | Start date of the current position, that is the one with no end date. |
| `currentPositions.companyId` | `_eq`, `_in`, `_is_null` | The profile currently works at this company. Takes the identifier of the LinkedIn company page, not a Jarvi company id. |
| `projectId` | `_eq`, `_in`, `_is_null` | The profile is associated with this project. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the profile. A parent status also matches profiles carrying any of its children, and the search is narrowed by the project or entity given in the context. Pass "other" for profiles whose status has no parent. |
| `hasProject` | `true`, `false` | The profile is associated with at least one project. |
| `projectCount` | `_eq`, `_gte`, `_lte`, `_is_null` | How many distinct projects the profile is associated with. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `isInCrm` | `true`, `false` | The profile is followed in the CRM, meaning it is a contact. Same as isContact. |
| `isInAts` | `true`, `false` | The profile is followed in the ATS, meaning it is a talent. Same as isTalent. |
| `hasAttachedFiles` | `true`, `false` | The profile has at least one attached file. |
| `isOpenToNewOpportunities` | `true`, `false` | The profile is flagged as open to new opportunities. |
| `isHiring` | `true`, `false` | The profile is flagged as hiring. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the profile, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Technical timestamp of the row creation. For the date the profile entered the base, use addedAt (added\_at) instead. |
| `addedAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the profile entered the group's base — what recruiters read as "added on". Set automatically when the profile is created or restored, unless the caller provides its own value, which is what imports do to keep the original date. |
| `externalId` | `_eq`, `_in` | Identifier of the profile in the client's own system — their ATS, their CRM, or whatever calls the public API. Never a Jarvi identifier. Unique per group, and used to recognise a profile that is sent again. |
| `publicIdentifier` | `_eq`, `_in` | LinkedIn public slug, the part after linkedin.com/in/. Unique per group. The person can change it on LinkedIn, which makes it the least reliable of the LinkedIn identifiers. |
| `linkedinId` | `_eq`, `_in` | Numeric LinkedIn identifier of the person. Unique per group. Not to be confused with memberId (member\_id), which holds the obfuscated "ACoAA…" identifier, nor with publicIdentifier (public\_identifier), which holds the URL slug. |
| `primaryLocale` | `_eq`, `_in`, `_is_null` | Main language of the profile itself — the language its LinkedIn page or CV is written in. Different from the profiles\_locales table, which lists the languages the person speaks and how well. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
### Companies
| Filter | Operators | What it matches |
| ---------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `companySearch` | `_search` | Searches company name and description, LinkedIn tagline and description, legal activity and NAF code, and custom fields — the target for keyword or industry searches. |
| `companyQuickSearch` | `_search` | Searches company name, email and phone — for name lookups only. |
| `companies.global` | `_search` | Company name and description. |
| `location` | `_near` | Geographic search. Use range 0 for an exact city or region match, or a radius in meters. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `hasLinkedinCompanyId` | `true`, `false` | The company is matched to a LinkedIn company page. |
| `hasContacts` | `true`, `false` | The company has at least one profile associated as a contact. |
| `hasProject` | `true`, `false` | The company has at least one project. |
| `isParentCompany` | `true`, `false` | The company has at least one subsidiary. |
| `isSubsidiary` | `true`, `false` | The company has a parent company. |
| `isProtected` | `true`, `false` | The company is under protection, meaning its protection date is still in the future. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the company. A parent status also matches companies carrying any of its children. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the company, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the company record was created. Data imports replace it with the creation date coming from the source ATS or CRM. |
| `externalId` | `_eq`, `_in` | Identifier of the company in the client's own system. Unique per group, and used as the matching key by imports and by the public API. |
| `linkedinCompanyId` | `_eq`, `_in` | Link to the LinkedIn page data of this company. That data is shared across all groups, and a given LinkedIn page can be attached to at most one company per group. |
| `parentId` | `_eq`, `_in`, `_is_null` | Parent company when this record is a subsidiary. Used as a single level: searching on a parent can include its direct subsidiaries, but not their own. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
### Projects
| Filter | Operators | What it matches |
| --------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectNameSearch` | `_search` | Searches project name, its company name and its reference id — the target for project lookups. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `hasJobBoardEnabled` | `true`, `false` | The project is published on at least one job board. |
| `hasLinkedinId` | `true`, `false` | The project is matched to a LinkedIn job posting. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the project. A parent status also matches projects carrying any of its children. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the project, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the project was created. Data imports replace it with the creation date coming from the source ATS or CRM. |
| `externalId` | `_eq`, `_in` | Identifier of the project in the client's own system. Unique per group, and used as the matching key by imports and by the public API. |
| `referenceId` | `_eq`, `_in` | The project reference id is used to map external job applications on website or jobboard to the project. |
| `companyId` | `_eq`, `_in`, `_is_null` | Client company the project is for. Optional — a project can exist without a company, and deleting the company clears the link rather than the project. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
Estas listas se generan desde el propio motor de búsqueda, así que siempre reflejan lo que acepta
la API. Las mismas definiciones se sirven como JSON Schema en [GET
/openapi](https://functions.prod.jarvi.tech/v1/public-api/openapi), bajo `ProfilesFilters`,
`CompaniesFilters` y `ProjectsFilters`.
## Ordenar
El parámetro `orderBy` de cada endpoint REST asocia un campo y un sentido:
```
?orderBy={"addedAt":"desc"}
```
Los campos que acepta un recurso están listados en la documentación del propio parámetro, en la página del endpoint. Ordenar por otra cosa vuelve al orden por defecto en lugar de fallar.
## Buscar por GraphQL
La query `search` devuelve identificadores y un total, que luego cargas en una segunda llamada. Ese reparto en dos pasos es lo que mantiene rápida una búsqueda, sea cual sea el tamaño de las fichas:
```graphql theme={null}
query FindDevelopers {
search(
resource: "profiles"
filters: { globalSearch: { _search: "javascript AND react" }, isTalent: true }
sort: { field: "addedAt", order: "DESC" }
limit: 50
) {
ids
total
}
}
```
```graphql theme={null}
query LoadThem($ids: [uuid!]!) {
profiles(where: { id: { _in: $ids } }) {
id
firstName
lastName
}
}
```
`resource` es `profiles`, `companies` o `projects`. `filters` toma el mismo objeto que el parámetro `where` de REST.
## Dejar atrás `_ilike`
| En lugar de | Escribe |
| --------------------------------------------------------- | --------------------------------------------- |
| `{"lastName": {"_ilike": "%Dupont%"}}` | `{"quickSearch": {"_search": "Dupont"}}` |
| `{"emailAddresses": {"email": {"_ilike": "%@acme.com"}}}` | `{"emails": {"_search": "acme.com"}}` |
| `{"headline": {"_ilike": "%developer%"}}` | `{"globalSearch": {"_search": "developer"}}` |
| `{"name": {"_ilike": "%Acme%"}}` (empresas) | `{"companyQuickSearch": {"_search": "Acme"}}` |
| `{"externalId": {"_ilike": "ABC-123"}}` | `{"externalId": {"_eq": "ABC-123"}}` |
Un identificador o una URL nunca es una búsqueda: usa `_eq`, que pasa por su propio índice.
¿Te falta algo, o no encuentras un filtro que esperabas? Escríbenos: la lista anterior la dirige el
motor, añadirle un filtro es un cambio pequeño.
# Enviar las candidaturas
Source: https://api-docs.jarvi.tech/es/use-cases/applicants
Esta API te permitirá enviar a Jarvi los candidatos de tu sitio web, de un formulario o de cualquier otra fuente.
Aquí están los pasos a seguir:
Si aún no tienes tus claves API, sigue los pasos en la sección
Obtener mis claves API
Consulta la documentación detallada de la API post applicants. También te será útil entender el concepto de Campos personalizados
No eres el primero, tranquilo, y probablemente encontrarás a continuación ejemplos de integraciones
similares a lo que deseas.
Aquí tienes un ejemplo de integración con Wordpress utilizando el plugin QuForm para los formularios.
```php theme={null}
add_action('quform_post_process_{ID_FORM}', function (array $result, Quform_Form $form){
$file_content = file_get_contents( $this->attachments[ 0 ] );
$file_base_64_content = base64_encode( $file_content );
$data = [
'firstName' => $form->getValue('quform_{ID_FORM}_5'),
'lastName' => $form->getValue('quform_{ID_FORM}_7'),
'fullName' => $form->getValue('quform_{ID_FORM}_6') . ' ' . $form->getValue('quform_{ID_FORM}_7'),
'cb9d4872-bfaa-4c57-b1af-91c008883386' => $form->getValue('quform_{ID_FORM}_10'), // Poste
'17fe3ebf-3641-4a94-ac9f-f11a6d429107' => $form->getValue('quform_{ID_FORM}_31'), // Exp SaaS
'6b751e8d-3947-43f9-af52-1db532210c6d' => $form->getValue('quform_{ID_FORM}_12'), // 1e année CDI
'region' => $form->getValue('quform_{ID_FORM}_15'),
'linkedinUrl' => $form->getValue('quform_{ID_FORM}_17'),
'phoneNumbers' => $form->getValue('quform_{ID_FORM}_20'),
'emailAddresses' => $form->getValue('quform_{ID_FORM}_22'),
'resumesFiles' => [
[
'data' => $file_base_64_content,
'fileName' => $this->attachments[0]->file_name,
],
],
];
wp_remote_post('https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants', [
'body' => json_encode($data),
'headers' => array(
'Content-Type' => 'application/json',
'X-Api-Key' => '{JARVI_PRIVATE_API_KEY}'
),
]);
return $result;
}, 10, 2);
```
Aquí tienes un ejemplo de integración en WebFlow. Esta integración también es válida para un sitio web clásico.
```javascript theme={null}
document.querySelector('#form-submit-button').addEventListener('click', function() {
var formElement = document.querySelector('.inscription-candidat-form');
var formData = new FormData(formElement);
var formDataAsObject = {}
for (var [key, value] of formData.entries()) {
console.log(key, value);
formDataAsObject[key] = value
}
if(!formDataAsObject.linkedinUrl) console.error("linkedinUrl is mandatory");
const data = {
"referenceId":"PROJECT-REFERENCE-ID",
...formDataAsObject
}
console.log("data to send",data)
fetch('https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants', {
method: 'POST',
mode:'cors',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'JARVI_PUBLIC_API_KEY'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => console.log("data sent",data))
.catch((error) => {
console.error('Error:', error);
});
});
```
Aquí tienes un ejemplo de integración en WebFlow. Esta integración también es válida para un sitio web clásico.
```javascript theme={null}
// Rellena estas constantes con tus propios valores
const FORM_SELECTOR = "#candidature-form";
const SUBMIT_BUTTON_SELECTOR = "#form-submit-button";
const RESUME_INPUT_NAME = "resumesFiles";
const PROJECT_REFERENCE_ID = "PROJECT-REFERENCE-ID";
const JARVI_PUBLIC_API_KEY = "YOUR JARVI PUBLIC API KEY";
// Función auxiliar para leer archivo como base64
const readFileAsBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => resolve(event.target.result);
reader.onerror = (error) => reject(error);
reader.readAsDataURL(file);
});
};
// Función auxiliar para validar datos del formulario
const validateFormData = (formData) => {
if (!formData.linkedinUrl) {
throw new Error("LinkedIn URL is mandatory");
}
return true;
};
// Manejador principal de envío del formulario
document.querySelector(SUBMIT_BUTTON_SELECTOR).addEventListener("click", async function (event) {
event.preventDefault(); // Prevenir el envío predeterminado del formulario
try {
const formElement = document.querySelector(FORM_SELECTOR);
if (!formElement) {
throw new Error("Form element not found");
}
// Convertir FormData a objeto
const formData = new FormData(formElement);
const formDataAsObject = Object.fromEntries(formData.entries());
// Validar datos del formulario
validateFormData(formDataAsObject);
// Obtener y validar archivo
const fileInput = formElement.querySelector(`input[name="${RESUME_INPUT_NAME}"]`);
if (!fileInput) {
throw new Error("Resume input field not found");
}
const file = fileInput.files[0];
if (!file) {
throw new Error("No file uploaded");
}
// Leer archivo y preparar datos
const base64Data = await readFileAsBase64(file);
const resumesFiles = [
{
fileName: file.name,
data: base64Data,
},
];
const data = {
referenceId: "PROJECT-REFERENCE-ID", // Actualiza con tu ID de referencia del proyecto
...formDataAsObject,
resumesFiles,
};
console.log("Sending data:", data);
// Enviar datos a la API
const response = await fetch("https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
"X-Api-Key": JARVI_PUBLIC_API_KEY,
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
console.log("Data sent successfully:", responseData);
// Opcional: Mostrar mensaje de éxito al usuario
alert("Application submitted successfully!");
} catch (error) {
console.error("Error:", error);
alert(`Error: ${error.message}`);
}
});
```
# Recuperar los proyectos
Source: https://api-docs.jarvi.tech/es/use-cases/projects
Esta API te permitirá recuperar todos los proyectos de reclutamiento, pero también de prospección y toda la información asociada. El objetivo suele ser mostrarlos en tu sitio web.
Aquí están los pasos a seguir:
Si aún no tienes tus claves de API, sigue los pasos en la sección
Obtener mis claves de API
Consulta la documentación detallada de la API GET projects. También te
será probablemente útil entender el concepto de Campos personalizados
Ya has recuperado los proyectos, quizás también quieras enviar las candidaturas a Jarvi:
Caso de uso: enviar candidaturas a Jarvi
# Obtenir mes clés d'API
Source: https://api-docs.jarvi.tech/fr/api-keys
Vos clés d'API vous permettront d'accéder à l'API
Rendez vous dans vos préférences, puis dans API. Cliquez alors sur le bouton permettant de générer les clés d'API comme indiqué ci-dessous.
Vous obtenez alors 2 clés :
* Une **clé publique**, qui ne permet que de créer des nouveaux profils, de nouvelles candidatures.
* Une **clé privée**, qui permet de tout faire. Cette clé ne vous sera affichée qu'une fois, à la création. Pensez donc à la noter dans un endroit sûr.
La clé privée ne doit pas être divulgée, ni présente dans le code javascript, accessible à l'utilisateur.
# [Candidature] Créer ou mettre à jour
Source: https://api-docs.jarvi.tech/fr/api-reference/applicants/post
POST /rest/v2/applicants
Save a new job application in Jarvi. It creates the profile if it does not exist, upload the file, link the profile to the recruitment project,...
Vous pouvez utiliser votre **clé d'api publique** pour cette requête. Cela vous permet de le faire directement dans le
code javascript du site.
La taille maximum de la requête est de 4.5MB. Nous vous conseillez de limiter la taille de votre champ d'upload de CV
à une taille inférieure.
Vous pouvez tout enregistrer sur le profil grâce aux Champs personnalisés. Cliquez sur ce lien pour comprendre comment lire et écrire des informations dans les champs personnalisés.
# [Entreprises] Lister
Source: https://api-docs.jarvi.tech/fr/api-reference/companies/get
GET /rest/v2/companies
Get all companies (recruitment, sales, etc.) with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Vous devez utiliser votre **clé d'api privée** pour cette requête.
Des changements sont prévus sur cette API à l'été 2025. Il est donc possible que des modifications soient nécessaires
de votre côté suite à des évolutions.
Vous pouvez tout personnaliser complètement les informations de chaque profils grâce aux Champs personnalisés. Cliquez sur ce lien pour comprendre comment lire et écrire des informations dans les champs personnalisés.
# [Entreprise] Créer ou mettre à jour
Source: https://api-docs.jarvi.tech/fr/api-reference/companies/post
POST /rest/v2/companies
Save a company in Jarvi. It creates the company if it does not exist, updates it if it does.
Vous devez utiliser votre **clé d'api privée** pour cette requête.
Vous pouvez tout personnaliser complètement les informations de chaque profils grâce aux Champs personnalisés. Cliquez sur ce lien pour comprendre comment lire et écrire des informations dans les champs personnalisés.
# GraphQL
Source: https://api-docs.jarvi.tech/fr/api-reference/graphql
POST /graphql
L'API complète pour faire tout le reste !
Cette API peut vous permettre de faire absolument tout ce que vous le souahitez. Vous pouvez personnaliser vos requêtes pour lire / écrire ce dont vous avez besoin.
Nous ne garantissons pas que les informations disponibles dans cette API ne changent pas sans préavis. Nous vous
conseillons de ne utiliser cette API pour des utilisations critiques sans nous en parler préalablement.
L'éditeur vous permet d'avoir une interface détaillant toutes les possibilités et vous permettant de tester vos
requêtes en direct.
Voici comment utiliser l'éditeur de requête en vidéo :
# [Offres] Lister
Source: https://api-docs.jarvi.tech/fr/api-reference/offers/get
GET /rest/v2/offers
Get all offers with their custom fields, files, and more.
Vous devez utiliser votre **clé d'api publique** pour cette requête. Vous devez mettre en cache les données de votre
côté pour respecter les limites de nombre d'appels par jour.
Vous pouvez tout personnaliser complètement les informations de chaque projet grâce aux Champs personnalisés. Cliquez sur ce lien pour comprendre comment lire et écrire des informations dans les champs personnalisés.
# [Profils] Lister
Source: https://api-docs.jarvi.tech/fr/api-reference/profiles/get
GET /rest/v2/profiles
Get all profiles with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Vous devez utiliser votre **clé d'api privée** pour cette requête.
Vous pouvez tout personnaliser complètement les informations de chaque profils grâce aux Champs personnalisés. Cliquez sur ce lien pour comprendre comment lire et écrire des informations dans les champs personnalisés.
# [Profils] Créer ou mettre à jour
Source: https://api-docs.jarvi.tech/fr/api-reference/profiles/post
POST /rest/v2/profiles
Save a profile in Jarvi. It creates the profile if it does not exist, updates it if it does.
Vous devez utiliser votre **clé d'api privée** pour cette requête.
Vous pouvez tout personnaliser complètement les informations de chaque profils grâce aux Champs personnalisés. Cliquez sur ce lien pour comprendre comment lire et écrire des informations dans les champs personnalisés.
# [Projets] Lister
Source: https://api-docs.jarvi.tech/fr/api-reference/projects/get
GET /rest/v2/projects
Get all projects (recruitment, sales, etc.) with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Vous devez utiliser votre **clé d'api privée** pour cette requête. Vous devez mettre en cache les données de votre
côté pour respecter les limites de nombre d'appels par jour.
Vous pouvez tout personnaliser complètement les informations de chaque projet grâce aux Champs personnalisés. Cliquez sur ce lien pour comprendre comment lire et écrire des informations dans les champs personnalisés.
# [Projets] Créer ou mettre à jour
Source: https://api-docs.jarvi.tech/fr/api-reference/projects/post
POST /rest/v2/projects
Save a project in Jarvi. It creates the project if it does not exist, updates it if it does.
Vous devez utiliser votre **clé d'api privée** pour cette requête.
Vous pouvez tout personnaliser complètement les informations de chaque profils grâce aux Champs personnalisés. Cliquez sur ce lien pour comprendre comment lire et écrire des informations dans les champs personnalisés.
# Champs personnalisés
Source: https://api-docs.jarvi.tech/fr/custom-fields
Comprendre la notion de champs personnalisés va vous permettre d'exploiter le plein potentiel de Jarvi et cette API
Les utilisateurs de Jarvi peuvent paramétrer les champs de saisie disponibles sur les
* profils (candidats ou prospects)
* projets (de recrutement ou de prospection)
* entreprises
Chaque champ est identifié par un ID unique (UUID).
Suivez ce lien pour accéder à Jarvi et obtenir la liste de tous les champs de saisie disponibles pour le cabinet de
recrutement.
Grace à l'API vous pouvez récupérer les informations dans ces champs, et les modifier également.
Il vous suffit pour cela d'en connaitre l'id unique (UUID).
En cliquant sur le lien ci-dessous, puis en ouvrant un champ, vous trouverez son id à la fin de l'url.
Par exemple, `https://app.jarvi.tech/#/settings/fields/cdccbea3-debd-453d-8a30-691f48c5a9e5` l'id est ici `cdccbea3-debd-453d-8a30-691f48c5a9e5`.
# Introduction
Source: https://api-docs.jarvi.tech/fr/introduction
Bienvenue sur la documentation de l'API Jarvi
## Pourquoi utiliser l'API Jarvi ?
Cette API peut vous permettre de faire absolument tout ce que vous le souahitez. Cette même API est utilisée par l'application Jarvi et son extension.
Voici les cas d'usage les plus courants :
Envoyez les candidatures provenant de votre site, de formulaires ou autre directement sur Jarvi.
Récupérez les projets, les offres d'emploi créés dans Jarvi et toutes les informations associées.
Mais n'oubliez pas, tout est possible.
## Conditions d'utilisation
* L'API Jarvi applique des limites basées sur le **temps d'exécution cumulé** de vos requêtes, et non sur leur nombre.
* Ces limites ne s'appliquent **que du lundi au vendredi, de 07:00 à 18:00 UTC** (08:00–19:00 heure de Paris). Attention, le créneau est ancré sur l'heure de Paris : à l'heure d'été il devient **06:00–17:00 UTC**. En dehors — nuits et week-ends — aucune limite n'est appliquée : c'est le moment idéal pour vos traitements par lots.
* Pendant les heures ouvrées, votre usage est limité à **5 minutes** de temps d'exécution cumulé par heure.
* Ces limites sont très largement dimensionnées et représentent environ **100 fois l'usage moyen** d'un utilisateur Jarvi.
* En cas de dépassement, l'API retourne une erreur `429` détaillant votre consommation, accompagnée d'un en-tête `Retry-After`.
* Si vous avez besoin d'une limite supérieure, contactez notre support — nous pouvons adapter les seuils à votre usage.
* Vous vous engagez à respecter la réglementation relative à la protection des données privées, en particulier le RGPD, pour toutes les données lues / écrites via l'API.
## Démarrage rapide
Vos clés d'API vous permettront d'accéder à l'API
# MCP (Assistants IA)
Source: https://api-docs.jarvi.tech/fr/mcp
Connectez des assistants IA comme Claude Desktop ou Cursor à vos données Jarvi
## Qu'est-ce que le MCP ?
Le [Model Context Protocol (MCP)](https://modelcontextprotocol.io) est un standard ouvert qui permet aux assistants IA de se connecter à des outils et sources de données externes. Jarvi expose un serveur MCP pour que vous puissiez utiliser des assistants IA pour rechercher, lire et mettre à jour vos données Jarvi directement.
## Clients compatibles
Tout client compatible MCP peut se connecter à Jarvi, notamment :
* [Claude Desktop](https://claude.ai/download)
* [Cursor](https://cursor.com)
* [Windsurf](https://codeium.com/windsurf)
* Tout client supportant le transport **Streamable HTTP**
## Configuration
### Claude Desktop
Allez dans **Paramètres > Connecteurs > Ajouter un connecteur personnalisé**, entrez un nom (ex : "Jarvi") et l'URL :
```
https://mcp.jarvi.tech
```
Claude Desktop gère l'authentification automatiquement via OAuth2 — vous serez redirigé pour vous connecter avec votre compte Jarvi.
### Autres clients
Pour les clients qui ne supportent pas OAuth2, utilisez une clé privée d'API :
* **Transport** : Streamable HTTP
* **URL** : `https://mcp.jarvi.tech`
* **Authentification** : header `x-api-key` avec votre [clé privée d'API](./api-keys)
Votre clé privée donne un accès complet à vos données Jarvi. Ne la partagez jamais publiquement.
## Outils disponibles
Une fois connecté, les outils suivants sont disponibles pour votre assistant IA :
| Outil | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------- |
| `whoAmI` | Retourne l'identité de l'utilisateur connecté (nom, email, workspace) |
| `getStatuses` | Lister les statuts disponibles pour les profils, entreprises ou projets |
| `getCustomFields` | Lister les champs personnalisés définis pour un type d'entité |
| `getHistorySubtypes` | Lister les sous-types personnalisés d'interactions (ex : catégories d'appels) |
| `advancedSearch` | Rechercher des profils, entreprises ou projets en langage naturel |
| `getEntityData` | Récupérer les données détaillées de profils, entreprises ou projets par IDs |
| `getAgenda` | Récupérer les événements à venir des agendas connectés |
| `getNotifications` | Récupérer les notifications récentes de l'utilisateur |
| `createProfile` | Créer un nouveau profil (talent et/ou contact) |
| `createCompany` | Créer une nouvelle entreprise |
| `createProject` | Créer un nouveau projet / mission de recrutement |
| `associateProfiles` | Lier un profil à un projet (talent ou contact) ou à une entreprise |
| `createHistoryEntry` | Ajouter une note ou un log d'appel à l'historique d'un profil |
| `createTodo` | Créer un rappel/todo lié à un profil, entreprise ou projet |
| `sendMessage` | Envoyer des messages à un ou plusieurs profils (email, LinkedIn, WhatsApp, Telegram, SMS) |
| `getMessageTemplates` | Lister les templates de messages disponibles (personnels et partagés par les collègues) |
| `uploadFile` | Uploader un fichier et le rattacher à une entité (profil, entreprise ou projet) |
| `cancelScheduledMessages` | Annuler un ou plusieurs messages programmés |
| `updateProfile` | Mettre à jour un profil, ajouter des emails ou numéros de téléphone |
| `updateCompany` | Mettre à jour une entreprise, changer le statut par nom, ajouter des emails |
| `updateProject` | Mettre à jour un projet, changer le statut par nom, lier une entreprise |
| `fillJobOffer` | Générer ou affiner une offre d'emploi à partir des données du projet |
| `getOutboxDiagnostic` | Diagnostiquer la boîte d'envoi : lister les messages programmés en attente avec statut et erreurs |
| `retryOutboxTasks` | Relancer les tâches en erreur en réinitialisant leur statut |
| `queryGraphQL` | Exécuter des requêtes/mutations GraphQL sur les tables autorisées (vos permissions s'appliquent) |
Toutes les opérations s'exécutent avec **vos permissions**. La sécurité au niveau des lignes s'applique — vous ne pouvez accéder qu'aux données auxquelles votre compte a accès.
## Exemples de prompts
Une fois connecté, essayez de demander à votre assistant IA :
* *"Qui suis-je ?"*
* *"Recherche des développeurs React à Paris"*
* *"Montre-moi les détails du projet X"*
* *"Crée un profil pour Jean Dupont, développeur"*
* *"Positionne ce candidat sur le projet X"*
* *"Mets le statut de l'entreprise Acme sur Qualifié"*
* *"Quels champs personnalisés existent sur les profils ?"*
* *"Note un appel avec Jean Dupont : a discuté de sa disponibilité"*
* *"Rappelle-moi de relancer Acme lundi prochain"*
* *"Mets à jour le titre de Jean Dupont en Développeur Senior"*
## Limites
* Les mêmes [limites d'utilisation](./introduction#conditions-dutilisation) s'appliquent, comptées sur le temps d'exécution cumulé. Si vous avez besoin de plus, contactez notre support.
* Les opérations de suppression sont bloquées — utilisez la suppression douce (mise à jour de `deletedAt`) à la place.
* Les réponses trop volumineuses sont automatiquement tronquées. Utilisez des arguments `limit` dans vos requêtes.
* Les outils destructifs (mutations, lancement de campagne, relance de messages) nécessitent une confirmation de l'assistant IA avant exécution.
# Recherche
Source: https://api-docs.jarvi.tech/fr/search
Filtrer profils, entreprises et projets sur des champs indexés avec l'opérateur _search
## Pourquoi `_search`
Jarvi fait tourner un moteur de recherche plein texte sur vos données. Les noms, emails, numéros de téléphone, intitulés de poste, compétences et valeurs de champs personnalisés sont indexés, et `_search` interroge ces index.
Filtrer avec `_ilike "%terme%"` fait tout autre chose : PostgreSQL parcourt chaque enregistrement de votre espace et le compare au motif. Sur une base de production, un seul `_ilike` sur un nom de famille a été mesuré à 2,4 secondes ; la même recherche via l'index répond en quelques dizaines de millisecondes.
`_ilike`, `_like`, `_nilike` et `_nlike` seront retirés de l'API publique le **31 juillet 2027**.
Sur les endpoints REST vous n'avez rien à faire — les filtres `_ilike` sont déjà traduits en
recherche indexée pour vous, et la réponse indique quel filtre a remplacé le vôtre. Via l'endpoint
`/graphql`, la requête vous appartient : elle peut viser n'importe quelle table, c'est donc à vous
de la changer.
## Utiliser l'opérateur
Un filtre prend le nom d'une cible de recherche et un terme :
```json theme={null}
{ "quickSearch": { "_search": "Dupont" } }
```
Combinez-en plusieurs avec `_and`, `_or` et `_not`, comme n'importe quel autre filtre :
```json theme={null}
{
"_and": [
{ "globalSearch": { "_search": "javascript" } },
{ "isTalent": true },
{ "addedAt": { "_gte": "2026-01-01" } }
]
}
```
### Syntaxe de requête
Le terme est lui-même une requête, pas une simple chaîne :
| Syntaxe | Correspond à |
| -------------------------------- | ----------------------------------------------------------- |
| `developer designer` | l'un ou l'autre mot |
| `developer AND designer` | les deux mots |
| `developer AND (java OR kotlin)` | les deux, avec l'une ou l'autre technologie |
| `NOT intern` | les enregistrements sans le mot |
| `"chef de projet"` | l'expression exacte, dans cet ordre |
| `java^10 python` | les deux mots, java pesant dix fois plus dans le classement |
### Des mots entiers, pas des fragments
Les noms sont indexés mot à mot, depuis le début : `Dupo` trouve *Dupont*, `upont` non. Il en va de même pour les noms d'entreprises et de projets.
Les emails et les numéros de téléphone sont indexés autrement et acceptent un fragment n'importe où : un numéro se retrouve par ses derniers chiffres, un email par son domaine.
## Les filtres, ressource par ressource
Tous les filtres ci-dessous s'appuient sur un index. Les autres colonnes et relations Hasura restent acceptées, mais elles ne sont ni optimisées ni garanties.
### Profiles
| Filter | Operators | What it matches |
| -------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quickSearch` | `_search` | Searches name, email, phone and short id — the target for simple person or name lookups. |
| `globalSearch` | `_search` | Searches name, headline, summary, position titles, company names, skills, educations and custom field values — the target for skill, role or technology searches. |
| `emailPhoneAndNameSearch` | `_search` | Searches name, email, phone and short id — the target for simple person or name lookups. |
| `fullPositionTitleSkillsAndTagsSearch` | `_search` | Searches name, headline, summary, position titles, company names, skills, educations and custom field values — the target for skill, role or technology searches. |
| `headline` | `_search` | Headline of the profile: the tagline shown under the name on LinkedIn, or the summary line extracted from the CV for profiles that have no LinkedIn page. Editable by hand only when the profile has no LinkedIn identifier. |
| `currentPositions.global` | `_search` | Full-text over title, description |
| `currentPositions.global.title` | `_search` | Job title held at this position, coming from LinkedIn, from a parsed CV or entered by hand. This is the main field behind job title search. |
| `currentPositions.global.description` | `_search` | What the person wrote about this role on their profile or CV. Often empty on imported records. |
| `emails` | `_search` | Email address of the profile or the company it belongs to. Normalized on save: lowercased, and when the input is not a clean address the first valid one found inside it is kept, the row being dropped if there is none. |
| `phones` | `_search` | Phone number search. Only the digits are compared, so a number matches whatever spacing, separators or country prefix it was typed with, and a suffix matches too. |
| `profiles.shortId` | `_search` | Short readable reference for the profile — what users type in quick search and what appears on generated documents. Assigned once at creation and never changes. |
| `historyEntries` | `_search` | Full-text over subject, message |
| `historyEntries.subject` | `_search` | Subject of the interaction: the email subject, the meeting title, or the job offer title. Empty for channels that carry no subject, such as LinkedIn messages, WhatsApp and SMS. |
| `historyEntries.message` | `_search` | Content of the interaction: the email body, the text of the LinkedIn, WhatsApp, SMS or Telegram message, or what the recruiter typed for a manual note. |
| `historyEntries.type` | `_eq`, `_in`, `_is_null` | What kind of interaction this entry records: emails, LinkedIn messages and InMails, connection invitations, SMS, WhatsApp, Telegram, phone calls, scheduled meetings, job applications, or a free note. |
| `historyEntries.subTypeId` | `_eq`, `_in`, `_is_null` | Optional sub-category of the interaction, defined by the group itself — naming its own kinds of notes or calls, for instance. Each sub-category belongs to a single parent type. |
| `historyEntries.occurredAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Single date used to place the interaction on the timeline: the meeting start for a scheduled event, the creation date otherwise. Maintained by a trigger. |
| `historyEntries.userId` | `_eq`, `_in`, `_is_null` | Jarvi user this entry belongs to. Determines the group the entry is attached to, and is used as its sender by default. |
| `historyEntries.hasFiles` | `true`, `false` | The profile has at least one interaction carrying an attached file. |
| `historyEntries.hasRecording` | `true`, `false` | The profile has at least one interaction with a meeting recording. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `location` | `_near` | Geographic search. Use range 0 for an exact city or region match, or a radius in meters. |
| `hasEmail` | `true`, `false` | The profile has at least one email address. |
| `hasEmailType` | `_eq`, `_in`, `_is_null` | The profile has an email address of this type. Takes the same values as the type of an email address. |
| `hasPhone` | `true`, `false` | The profile has at least one phone number. |
| `hasValidPhone` | `true`, `false` | The profile has at least one phone number not marked as invalid. |
| `hasLocation` | `true`, `false` | The profile has at least one location. |
| `hasPhoneType` | `_eq`, `_in`, `_is_null` | The profile has a phone number of this type. Takes the same values as the type of a phone number. |
| `hasWhatsApp` | `true`, `false` | The profile has a phone number reachable on WhatsApp. |
| `isTalent` | `true`, `false` | The profile is a talent — someone you recruit, followed in the ATS. A profile can be a talent and a contact at once. |
| `isContact` | `true`, `false` | The profile is a contact — a client or a hiring manager, followed in the CRM. A profile can be a contact and a talent at once. |
| `hasLinkedin` | `true`, `false` | The profile is matched to a LinkedIn account. |
| `hasResume` | `true`, `false` | The profile has at least one CV attached. |
| `isConnectedOnLinkedinWithMe` | `true`, `false` | The calling user is connected with this profile on LinkedIn, as recorded in the profile's history. |
| `hasReplied` | `true`, `false` | The profile has answered at least once, on any channel. |
| `hasScheduledMessage` | `true`, `false` | A message or a connection invitation is still waiting to be sent to this profile. |
| `hasUnreadMessagesNotifications` | `true`, `false` | The profile has an unread message notification. |
| `firstPositionStartDate` | `_eq`, `_gte`, `_lte`, `_is_null` | Start date of the earliest position on the profile — the usual way to reason about years of experience. |
| `lastPositionStartDate` | `_eq`, `_gte`, `_lte`, `_is_null` | Start date of the current position, that is the one with no end date. |
| `currentPositions.companyId` | `_eq`, `_in`, `_is_null` | The profile currently works at this company. Takes the identifier of the LinkedIn company page, not a Jarvi company id. |
| `projectId` | `_eq`, `_in`, `_is_null` | The profile is associated with this project. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the profile. A parent status also matches profiles carrying any of its children, and the search is narrowed by the project or entity given in the context. Pass "other" for profiles whose status has no parent. |
| `hasProject` | `true`, `false` | The profile is associated with at least one project. |
| `projectCount` | `_eq`, `_gte`, `_lte`, `_is_null` | How many distinct projects the profile is associated with. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `isInCrm` | `true`, `false` | The profile is followed in the CRM, meaning it is a contact. Same as isContact. |
| `isInAts` | `true`, `false` | The profile is followed in the ATS, meaning it is a talent. Same as isTalent. |
| `hasAttachedFiles` | `true`, `false` | The profile has at least one attached file. |
| `isOpenToNewOpportunities` | `true`, `false` | The profile is flagged as open to new opportunities. |
| `isHiring` | `true`, `false` | The profile is flagged as hiring. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the profile, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Technical timestamp of the row creation. For the date the profile entered the base, use addedAt (added\_at) instead. |
| `addedAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the profile entered the group's base — what recruiters read as "added on". Set automatically when the profile is created or restored, unless the caller provides its own value, which is what imports do to keep the original date. |
| `externalId` | `_eq`, `_in` | Identifier of the profile in the client's own system — their ATS, their CRM, or whatever calls the public API. Never a Jarvi identifier. Unique per group, and used to recognise a profile that is sent again. |
| `publicIdentifier` | `_eq`, `_in` | LinkedIn public slug, the part after linkedin.com/in/. Unique per group. The person can change it on LinkedIn, which makes it the least reliable of the LinkedIn identifiers. |
| `linkedinId` | `_eq`, `_in` | Numeric LinkedIn identifier of the person. Unique per group. Not to be confused with memberId (member\_id), which holds the obfuscated "ACoAA…" identifier, nor with publicIdentifier (public\_identifier), which holds the URL slug. |
| `primaryLocale` | `_eq`, `_in`, `_is_null` | Main language of the profile itself — the language its LinkedIn page or CV is written in. Different from the profiles\_locales table, which lists the languages the person speaks and how well. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
### Companies
| Filter | Operators | What it matches |
| ---------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `companySearch` | `_search` | Searches company name and description, LinkedIn tagline and description, legal activity and NAF code, and custom fields — the target for keyword or industry searches. |
| `companyQuickSearch` | `_search` | Searches company name, email and phone — for name lookups only. |
| `companies.global` | `_search` | Company name and description. |
| `location` | `_near` | Geographic search. Use range 0 for an exact city or region match, or a radius in meters. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `hasLinkedinCompanyId` | `true`, `false` | The company is matched to a LinkedIn company page. |
| `hasContacts` | `true`, `false` | The company has at least one profile associated as a contact. |
| `hasProject` | `true`, `false` | The company has at least one project. |
| `isParentCompany` | `true`, `false` | The company has at least one subsidiary. |
| `isSubsidiary` | `true`, `false` | The company has a parent company. |
| `isProtected` | `true`, `false` | The company is under protection, meaning its protection date is still in the future. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the company. A parent status also matches companies carrying any of its children. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the company, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the company record was created. Data imports replace it with the creation date coming from the source ATS or CRM. |
| `externalId` | `_eq`, `_in` | Identifier of the company in the client's own system. Unique per group, and used as the matching key by imports and by the public API. |
| `linkedinCompanyId` | `_eq`, `_in` | Link to the LinkedIn page data of this company. That data is shared across all groups, and a given LinkedIn page can be attached to at most one company per group. |
| `parentId` | `_eq`, `_in`, `_is_null` | Parent company when this record is a subsidiary. Used as a single level: searching on a parent can include its direct subsidiaries, but not their own. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
### Projects
| Filter | Operators | What it matches |
| --------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectNameSearch` | `_search` | Searches project name, its company name and its reference id — the target for project lookups. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `hasJobBoardEnabled` | `true`, `false` | The project is published on at least one job board. |
| `hasLinkedinId` | `true`, `false` | The project is matched to a LinkedIn job posting. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the project. A parent status also matches projects carrying any of its children. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the project, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the project was created. Data imports replace it with the creation date coming from the source ATS or CRM. |
| `externalId` | `_eq`, `_in` | Identifier of the project in the client's own system. Unique per group, and used as the matching key by imports and by the public API. |
| `referenceId` | `_eq`, `_in` | The project reference id is used to map external job applications on website or jobboard to the project. |
| `companyId` | `_eq`, `_in`, `_is_null` | Client company the project is for. Optional — a project can exist without a company, and deleting the company clears the link rather than the project. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
Ces listes sont générées depuis le moteur de recherche lui-même : elles correspondent toujours à ce
que l'API accepte. Les mêmes définitions sont servies en JSON Schema par [GET
/openapi](https://functions.prod.jarvi.tech/v1/public-api/openapi), sous `ProfilesFilters`,
`CompaniesFilters` et `ProjectsFilters`.
## Trier
Le paramètre `orderBy` de chaque endpoint REST associe un champ et un sens :
```
?orderBy={"addedAt":"desc"}
```
Les champs acceptés par une ressource sont listés dans la documentation du paramètre, sur la page de l'endpoint. Trier sur autre chose retombe sur l'ordre par défaut plutôt que d'échouer.
## Chercher via GraphQL
La query `search` renvoie des identifiants et un total, que vous chargez ensuite en un second appel. Ce découpage en deux temps est ce qui garde une recherche rapide, quelle que soit la taille des fiches :
```graphql theme={null}
query FindDevelopers {
search(
resource: "profiles"
filters: { globalSearch: { _search: "javascript AND react" }, isTalent: true }
sort: { field: "addedAt", order: "DESC" }
limit: 50
) {
ids
total
}
}
```
```graphql theme={null}
query LoadThem($ids: [uuid!]!) {
profiles(where: { id: { _in: $ids } }) {
id
firstName
lastName
}
}
```
`resource` vaut `profiles`, `companies` ou `projects`. `filters` prend le même objet que le paramètre `where` du REST.
## Quitter `_ilike`
| Au lieu de | Écrivez |
| --------------------------------------------------------- | --------------------------------------------- |
| `{"lastName": {"_ilike": "%Dupont%"}}` | `{"quickSearch": {"_search": "Dupont"}}` |
| `{"emailAddresses": {"email": {"_ilike": "%@acme.com"}}}` | `{"emails": {"_search": "acme.com"}}` |
| `{"headline": {"_ilike": "%developer%"}}` | `{"globalSearch": {"_search": "developer"}}` |
| `{"name": {"_ilike": "%Acme%"}}` (entreprises) | `{"companyQuickSearch": {"_search": "Acme"}}` |
| `{"externalId": {"_ilike": "ABC-123"}}` | `{"externalId": {"_eq": "ABC-123"}}` |
Un identifiant ou une URL n'est jamais une recherche : utilisez `_eq`, qui passe par son propre index.
Il vous manque quelque chose, ou un filtre que vous attendiez n'y est pas ? Écrivez-nous — la liste
ci-dessus est pilotée par le moteur, y ajouter un filtre est un petit changement.
# Envoyer les candidatures
Source: https://api-docs.jarvi.tech/fr/use-cases/applicants
Cette API va vous permettre d'envoyer dans Jarvi les candidats de votre site, d'un formulaire, ou n'importe quelle autre source.
Voici la marche à suivre :
Si vous n'avez pas déjà vos clés d'API, suivez la marche à suivre dans la rubrique
Obtenir mes clés d'API
Consultez la documentation détaillée de l'API post applicants. Il vous sera sans doute aussi utile de comprendre la notion de Champs personnalisés
Vous n'êtes pas le premier rassurez vous, et vous trouverez probablement ci-dessous des exemples d'intégrations
similaires à votre souhait.
Voici un exemple d'intégration Wordpress utilisant le plugin QuForm pour les formulaires.
```php theme={null}
add_action('quform_post_process_{ID_FORM}', function (array $result, Quform_Form $form){
$file_content = file_get_contents( $this->attachments[ 0 ] );
$file_base_64_content = base64_encode( $file_content );
$data = [
'firstName' => $form->getValue('quform_{ID_FORM}_5'),
'lastName' => $form->getValue('quform_{ID_FORM}_7'),
'fullName' => $form->getValue('quform_{ID_FORM}_6') . ' ' . $form->getValue('quform_{ID_FORM}_7'),
'cb9d4872-bfaa-4c57-b1af-91c008883386' => $form->getValue('quform_{ID_FORM}_10'), // Poste
'17fe3ebf-3641-4a94-ac9f-f11a6d429107' => $form->getValue('quform_{ID_FORM}_31'), // Exp SaaS
'6b751e8d-3947-43f9-af52-1db532210c6d' => $form->getValue('quform_{ID_FORM}_12'), // 1e année CDI
'region' => $form->getValue('quform_{ID_FORM}_15'),
'linkedinUrl' => $form->getValue('quform_{ID_FORM}_17'),
'phoneNumbers' => $form->getValue('quform_{ID_FORM}_20'),
'emailAddresses' => $form->getValue('quform_{ID_FORM}_22'),
'resumesFiles' => [
[
'data' => $file_base_64_content,
'fileName' => $this->attachments[0]->file_name,
],
],
];
wp_remote_post('https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants', [
'body' => json_encode($data),
'headers' => array(
'Content-Type' => 'application/json',
'X-Api-Key' => '{JARVI_PRIVATE_API_KEY}'
),
]);
return $result;
}, 10, 2);
```
Voici un exemple d'intégration dans WebFlow. Cette intégration est valable aussi pour un site web classique.
```javascript theme={null}
document.querySelector('#form-submit-button').addEventListener('click', function() {
var formElement = document.querySelector('.inscription-candidat-form');
var formData = new FormData(formElement);
var formDataAsObject = {}
for (var [key, value] of formData.entries()) {
console.log(key, value);
formDataAsObject[key] = value
}
if(!formDataAsObject.linkedinUrl) console.error("linkedinUrl is mandatory");
const data = {
"referenceId":"PROJECT-REFERENCE-ID",
...formDataAsObject
}
console.log("data to send",data)
fetch('https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants', {
method: 'POST',
mode:'cors',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'JARVI_PUBLIC_API_KEY'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => console.log("data sent",data))
.catch((error) => {
console.error('Error:', error);
});
});
```
Voici un exemple d'intégration dans WebFlow. Cette intégration est valable aussi pour un site web classique.
```javascript theme={null}
// Fill those constants with your own values
const FORM_SELECTOR = "#candidature-form";
const SUBMIT_BUTTON_SELECTOR = "#form-submit-button";
const RESUME_INPUT_NAME = "resumesFiles";
const PROJECT_REFERENCE_ID = "PROJECT-REFERENCE-ID";
const JARVI_PUBLIC_API_KEY = "YOUR JARVI PUBLIC API KEY";
// Helper function to read file as base64
const readFileAsBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => resolve(event.target.result);
reader.onerror = (error) => reject(error);
reader.readAsDataURL(file);
});
};
// Helper function to validate form data
const validateFormData = (formData) => {
if (!formData.linkedinUrl) {
throw new Error("LinkedIn URL is mandatory");
}
return true;
};
// Main form submission handler
document.querySelector(SUBMIT_BUTTON_SELECTOR).addEventListener("click", async function (event) {
event.preventDefault(); // Prevent default form submission
try {
const formElement = document.querySelector(FORM_SELECTOR);
if (!formElement) {
throw new Error("Form element not found");
}
// Convert FormData to object
const formData = new FormData(formElement);
const formDataAsObject = Object.fromEntries(formData.entries());
// Validate form data
validateFormData(formDataAsObject);
// Get and validate file
const fileInput = formElement.querySelector(`input[name="${RESUME_INPUT_NAME}"]`);
if (!fileInput) {
throw new Error("Resume input field not found");
}
const file = fileInput.files[0];
if (!file) {
throw new Error("No file uploaded");
}
// Read file and prepare data
const base64Data = await readFileAsBase64(file);
const resumesFiles = [
{
fileName: file.name,
data: base64Data,
},
];
const data = {
referenceId: "PROJECT-REFERENCE-ID", // Update with your project reference ID
...formDataAsObject,
resumesFiles,
};
console.log("Sending data:", data);
// Send data to API
const response = await fetch("https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
"X-Api-Key": JARVI_PUBLIC_API_KEY,
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
console.log("Data sent successfully:", responseData);
// Optional: Show success message to user
alert("Application submitted successfully!");
} catch (error) {
console.error("Error:", error);
alert(`Error: ${error.message}`);
}
});
```
# Récupérer les projets
Source: https://api-docs.jarvi.tech/fr/use-cases/projects
Cette API va vous permettre de récupérer tous les projets de recrutement mais aussi de prospections et toutes les informations associées. L'objectif est souvent de les afficher sur votre site web.
Voici la marche à suivre :
Si vous n'avez pas déjà vos clés d'API, suivez la marche à suivre dans la rubrique
Obtenir mes clés d'API
Consultez la documentation détaillée de l'API GET projects. Il vous sera
sans doute aussi utile de comprendre la notion de Champs personnalisés
Vous avez récupéré les projets, peut-être soufaitez vous aussi envoyer les candidatures sur jarvi :
Cas d'usage : envoyer les candidatures dans Jarvi
# Ottieni le mie chiavi API
Source: https://api-docs.jarvi.tech/it/api-keys
Le tue chiavi API ti permetteranno di accedere all'API
Vai nelle tue preferenze, poi in API. Clicca quindi sul pulsante per generare le chiavi API come indicato qui sotto.
Otterrai 2 chiavi:
* Una **chiave pubblica**, che permette solo di creare nuovi profili e nuove candidature.
* Una **chiave privata**, che permette di fare tutto. Questa chiave ti verrà mostrata solo una volta, al momento della creazione. Ricordati quindi di annotarla in un posto sicuro.
La chiave privata non deve essere divulgata, né presente nel codice javascript, accessibile all'utente.
# [Candidatura] Creare o aggiornare
Source: https://api-docs.jarvi.tech/it/api-reference/applicants/post
POST /rest/v2/applicants
Save a new job application in Jarvi. It creates the profile if it does not exist, upload the file, link the profile to the recruitment project,...
Puoi utilizzare la tua **chiave api pubblica** per questa richiesta. Questo ti permette di farlo direttamente nel
codice javascript del sito.
La dimensione massima della richiesta è di 4.5MB. Ti consigliamo di limitare la dimensione del tuo campo di upload del
CV a una dimensione inferiore.
Puoi salvare tutto sul profilo grazie ai Campi personalizzati. Clicca su questo link per capire come leggere e scrivere informazioni nei campi personalizzati.
# [Aziende] Elencare
Source: https://api-docs.jarvi.tech/it/api-reference/companies/get
GET /rest/v2/companies
Get all companies (recruitment, sales, etc.) with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Devi utilizzare la tua **chiave API privata** per questa richiesta.
Sono previsti cambiamenti a questa API nell'estate del 2025. È quindi possibile che siano necessarie modifiche da
parte tua a seguito di questi sviluppi.
Puoi personalizzare completamente le informazioni di ogni profilo grazie ai Campi personalizzati. Clicca su questo link per capire come leggere e scrivere informazioni nei campi personalizzati.
# [Azienda] Creare o aggiornare
Source: https://api-docs.jarvi.tech/it/api-reference/companies/post
POST /rest/v2/companies
Save a company in Jarvi. It creates the company if it does not exist, updates it if it does.
Devi utilizzare la tua **chiave api privata** per questa richiesta.
Puoi personalizzare completamente le informazioni di ogni profilo grazie ai Campi personalizzati. Clicca su questo link per capire come leggere e scrivere informazioni nei campi personalizzati.
# GraphQL
Source: https://api-docs.jarvi.tech/it/api-reference/graphql
POST /graphql
L'API completa per fare tutto il resto!
Questa API ti permette di fare assolutamente tutto ciò che desideri. Puoi personalizzare le tue richieste per leggere/scrivere tutto ciò di cui hai bisogno.
Non garantiamo che le informazioni disponibili in questa API non cambino senza preavviso. Ti consigliamo di non
utilizzare questa API per usi critici senza parlarcene prima.
L'editor ti offre un'interfaccia che mostra tutte le possibilità e ti permette di testare le tue richieste in tempo
reale.
Ecco come utilizzare l'editor di richieste in video:
# [Offerte] Elencare
Source: https://api-docs.jarvi.tech/it/api-reference/offers/get
GET /rest/v2/offers
Get all offers with their custom fields, files, and more.
Devi utilizzare la tua **chiave api pubblica** per questa richiesta. Devi memorizzare nella cache i dati dal tuo lato
per rispettare i limiti del numero di chiamate giornaliere.
Puoi personalizzare completamente le informazioni di ogni progetto grazie ai Campi personalizzati. Clicca su questo link per capire come leggere e scrivere informazioni nei campi personalizzati.
# [Profili] Elencare
Source: https://api-docs.jarvi.tech/it/api-reference/profiles/get
GET /rest/v2/profiles
Get all profiles with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Devi utilizzare la tua **chiave api privata** per questa richiesta.
Puoi personalizzare completamente le informazioni di ogni profilo grazie ai Campi personalizzati. Clicca su questo link per capire come leggere e scrivere informazioni nei campi personalizzati.
# [Profili] Creare o aggiornare
Source: https://api-docs.jarvi.tech/it/api-reference/profiles/post
POST /rest/v2/profiles
Save a profile in Jarvi. It creates the profile if it does not exist, updates it if it does.
Devi utilizzare la tua **chiave api privata** per questa richiesta.
Puoi personalizzare completamente le informazioni di ogni profilo grazie ai Campi personalizzati. Clicca su questo link per capire come leggere e scrivere informazioni nei campi personalizzati.
# [Progetti] Elencare
Source: https://api-docs.jarvi.tech/it/api-reference/projects/get
GET /rest/v2/projects
Get all projects (recruitment, sales, etc.) with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Devi utilizzare la tua **chiave api privata** per questa richiesta. Devi memorizzare nella cache i dati dal tuo lato
per rispettare i limiti del numero di chiamate giornaliere.
Puoi personalizzare completamente le informazioni di ogni progetto grazie ai Campi personalizzati. Clicca su questo link per capire come leggere e scrivere informazioni nei campi personalizzati.
# [Progetti] Creare o aggiornare
Source: https://api-docs.jarvi.tech/it/api-reference/projects/post
POST /rest/v2/projects
Save a project in Jarvi. It creates the project if it does not exist, updates it if it does.
Devi utilizzare la tua **chiave api privata** per questa richiesta.
Puoi personalizzare completamente le informazioni di ogni profilo grazie ai Campi personalizzati. Clicca su questo link per capire come leggere e scrivere informazioni nei campi personalizzati.
# Campi personalizzati
Source: https://api-docs.jarvi.tech/it/custom-fields
Comprendere il concetto di campi personalizzati ti permetterà di sfruttare il pieno potenziale di Jarvi e questa API
Gli utenti di Jarvi possono configurare i campi di inserimento disponibili sui
* profili (candidati o prospect)
* progetti (di reclutamento o di prospezione)
* aziende
Ogni campo è identificato da un ID unico (UUID).
Segui questo link per accedere a Jarvi e ottenere la lista di tutti i campi di inserimento disponibili per l'agenzia
di reclutamento.
Grazie all'API puoi recuperare le informazioni in questi campi, e modificarle anche.
Ti basta conoscerne l'id unico (UUID).
Cliccando sul link qui sotto, e poi aprendo un campo, troverai il suo id alla fine dell'url.
Per esempio, `https://app.jarvi.tech/#/settings/fields/cdccbea3-debd-453d-8a30-691f48c5a9e5` l'id qui è `cdccbea3-debd-453d-8a30-691f48c5a9e5`.
# Introduzione
Source: https://api-docs.jarvi.tech/it/introduction
Benvenuto nella documentazione dell'API Jarvi
## Perché utilizzare l'API Jarvi?
Questa API ti permette di fare assolutamente tutto ciò che desideri. La stessa API è utilizzata dall'applicazione Jarvi e dalla sua estensione.
Ecco i casi d'uso più comuni:
Invia le candidature provenienti dal tuo sito, da moduli o altro direttamente su Jarvi.
Recupera i progetti, le offerte di lavoro create in Jarvi e tutte le informazioni associate.
Ma non dimenticare, tutto è possibile.
## Condizioni d'uso
* L'API Jarvi applica limiti basati sul **tempo di esecuzione cumulativo** delle tue richieste, non sul loro numero.
* Questi limiti si applicano **solo dal lunedì al venerdì, dalle 07:00 alle 18:00 UTC** (06:00–17:00 UTC con l'ora legale). Al di fuori — notti e fine settimana — non viene applicato alcun limite: è il momento ideale per le tue elaborazioni batch.
* Durante l'orario lavorativo, il tuo utilizzo è limitato a **5 minuti** di tempo di esecuzione cumulativo all'ora.
* Questi limiti sono molto generosi e rappresentano circa **100 volte l'utilizzo medio** di un utente Jarvi.
* In caso di superamento, l'API restituisce un errore `429` che dettaglia il tuo consumo, accompagnato da un header `Retry-After`.
* Se hai bisogno di un limite superiore, contatta il nostro supporto — possiamo adattare le soglie al tuo utilizzo.
* Ti impegni a rispettare la normativa relativa alla protezione dei dati personali, in particolare il GDPR, per tutti i dati letti/scritti tramite l'API.
## Avvio rapido
Le tue chiavi API ti permetteranno di accedere all'API
# MCP (Assistenti IA)
Source: https://api-docs.jarvi.tech/it/mcp
Collega assistenti IA come Claude Desktop o Cursor ai tuoi dati Jarvi
## Cos'è MCP?
Il [Model Context Protocol (MCP)](https://modelcontextprotocol.io) è uno standard aperto che permette agli assistenti IA di connettersi a strumenti e fonti di dati esterni. Jarvi espone un server MCP così puoi usare assistenti IA per cercare, leggere e aggiornare i tuoi dati Jarvi direttamente.
## Client compatibili
Qualsiasi client compatibile con MCP può connettersi a Jarvi, tra cui:
* [Claude Desktop](https://claude.ai/download)
* [Cursor](https://cursor.com)
* [Windsurf](https://codeium.com/windsurf)
* Qualsiasi client che supporta il trasporto **Streamable HTTP**
## Configurazione
### Claude Desktop
Vai su **Impostazioni > Connettori > Aggiungi un connettore personalizzato**, inserisci un nome (es: "Jarvi") e l'URL:
```
https://mcp.jarvi.tech
```
Claude Desktop gestisce l'autenticazione automaticamente tramite OAuth2 — verrai reindirizzato per accedere con il tuo account Jarvi.
### Altri client
Per i client che non supportano OAuth2, utilizza una chiave API privata:
* **Trasporto**: Streamable HTTP
* **URL**: `https://mcp.jarvi.tech`
* **Autenticazione**: header `x-api-key` con la tua [chiave API privata](./api-keys)
La tua chiave privata garantisce accesso completo ai tuoi dati Jarvi. Non condividerla mai pubblicamente.
## Strumenti disponibili
Una volta connesso, i seguenti strumenti sono disponibili per il tuo assistente IA:
| Strumento | Descrizione |
| ------------------------- | ------------------------------------------------------------------------------------------------ |
| `whoAmI` | Restituisce l'identità dell'utente connesso (nome, email, workspace) |
| `getStatuses` | Elencare gli stati disponibili per profili, aziende o progetti |
| `getCustomFields` | Elencare i campi personalizzati definiti per un tipo di entità |
| `getHistorySubtypes` | Elencare i sottotipi personalizzati di interazioni (es: categorie di chiamate) |
| `advancedSearch` | Cercare profili, aziende o progetti in linguaggio naturale |
| `getEntityData` | Recuperare i dati dettagliati di profili, aziende o progetti tramite ID |
| `getAgenda` | Recuperare gli eventi imminenti dalle agende connesse |
| `getNotifications` | Recuperare le notifiche recenti dell'utente |
| `createProfile` | Creare un nuovo profilo (talento e/o contatto) |
| `createCompany` | Creare una nuova azienda |
| `createProject` | Creare un nuovo progetto / missione di recruiting |
| `associateProfiles` | Collegare un profilo a un progetto (talento o contatto) o a un'azienda |
| `createHistoryEntry` | Aggiungere una nota o un log di chiamata alla cronologia di un profilo |
| `createTodo` | Creare un promemoria/todo collegato a un profilo, azienda o progetto |
| `sendMessage` | Inviare messaggi a uno o più profili (email, LinkedIn, WhatsApp, Telegram, SMS) |
| `getMessageTemplates` | Elencare i template di messaggi disponibili (personali e condivisi dai colleghi) |
| `uploadFile` | Caricare un file e collegarlo a un'entità (profilo, azienda o progetto) |
| `cancelScheduledMessages` | Annullare uno o più messaggi programmati |
| `updateProfile` | Aggiornare un profilo, aggiungere email o numeri di telefono |
| `updateCompany` | Aggiornare un'azienda, modificare lo stato per nome, aggiungere email |
| `updateProject` | Aggiornare un progetto, modificare lo stato per nome, collegare un'azienda |
| `fillJobOffer` | Generare o perfezionare un'offerta di lavoro partendo dai dati del progetto |
| `getOutboxDiagnostic` | Diagnosticare la casella di invio: elencare i messaggi programmati in attesa con stato ed errori |
| `retryOutboxTasks` | Rilanciare le attività in errore reimpostando il loro stato |
| `queryGraphQL` | Eseguire query/mutazioni GraphQL sulle tabelle autorizzate (si applicano i tuoi permessi) |
Tutte le operazioni vengono eseguite con **i tuoi permessi**. Si applica la sicurezza a livello di riga — puoi accedere solo ai dati a cui il tuo account ha accesso.
## Esempi di prompt
Una volta connesso, prova a chiedere al tuo assistente IA:
* *"Chi sono?"*
* *"Cerca sviluppatori React a Parigi"*
* *"Mostrami i dettagli del progetto X"*
* *"Crea un profilo per Jean Dupont, sviluppatore"*
* *"Posiziona questo candidato sul progetto X"*
* *"Imposta lo stato dell'azienda Acme su Qualificato"*
* *"Quali campi personalizzati esistono sui profili?"*
* *"Annota una chiamata con Jean Dupont: discusso della sua disponibilità"*
* *"Ricordami di ricontattare Acme lunedì prossimo"*
* *"Aggiorna il titolo di Jean Dupont in Sviluppatore Senior"*
## Limitazioni
* Si applicano gli stessi [limiti di utilizzo](./introduction#condizioni-duso), calcolati sul tempo di esecuzione cumulato. Se ti serve di più, contatta il nostro supporto.
* Le operazioni di eliminazione sono bloccate — usa l'eliminazione soft (aggiornamento di `deletedAt`) invece.
* Le risposte troppo voluminose vengono automaticamente troncate. Usa argomenti `limit` nelle tue richieste.
* Gli strumenti distruttivi (mutazioni, lancio di campagne, reinvio di messaggi) richiedono una conferma dell'assistente IA prima dell'esecuzione.
# Ricerca
Source: https://api-docs.jarvi.tech/it/search
Filtrare profili, aziende e progetti su campi indicizzati con l'operatore _search
## Perché `_search`
Jarvi fa girare un motore di ricerca full-text sui tuoi dati. Nomi, email, numeri di telefono, titoli di lavoro, competenze e valori dei campi personalizzati sono indicizzati, e `_search` interroga quegli indici.
Filtrare con `_ilike "%termine%"` fa tutt'altro: PostgreSQL scorre ogni record del tuo spazio e lo confronta con il pattern. Su una base di produzione, un solo `_ilike` su un cognome è stato misurato a 2,4 secondi; la stessa ricerca tramite l'indice risponde in qualche decina di millisecondi.
`_ilike`, `_like`, `_nilike` e `_nlike` saranno ritirati dall'API pubblica il **31 luglio 2027**.
Sugli endpoint REST non devi fare nulla: i filtri `_ilike` vengono già tradotti in una ricerca
indicizzata al posto tuo, e la risposta indica quale filtro ha sostituito il tuo. Tramite
l'endpoint `/graphql` la query è tua e può puntare a qualunque tabella, quindi sta a te cambiarla.
## Usare l'operatore
Un filtro prende il nome di un bersaglio di ricerca e un termine:
```json theme={null}
{ "quickSearch": { "_search": "Dupont" } }
```
Combinane più d'uno con `_and`, `_or` e `_not`, come con qualsiasi altro filtro:
```json theme={null}
{
"_and": [
{ "globalSearch": { "_search": "javascript" } },
{ "isTalent": true },
{ "addedAt": { "_gte": "2026-01-01" } }
]
}
```
### Sintassi della query
Il termine è esso stesso una query, non una semplice stringa:
| Sintassi | Corrisponde a |
| -------------------------------- | -------------------------------------------------------------------- |
| `developer designer` | una delle due parole |
| `developer AND designer` | entrambe le parole |
| `developer AND (java OR kotlin)` | entrambe, con l'una o l'altra tecnologia |
| `NOT intern` | i record senza quella parola |
| `"project manager"` | la frase esatta, in quell'ordine |
| `java^10 python` | entrambe le parole, con java che pesa dieci volte di più nel ranking |
### Parole intere, non frammenti
I nomi sono indicizzati parola per parola, dall'inizio: `Dupo` trova *Dupont*, `upont` no. Lo stesso vale per i nomi di aziende e progetti.
Email e numeri di telefono sono indicizzati diversamente e accettano un frammento in qualsiasi posizione: un numero si ritrova dalle ultime cifre, un'email dal suo dominio.
## I filtri, risorsa per risorsa
Tutti i filtri qui sotto si appoggiano a un indice. Le altre colonne e relazioni Hasura restano accettate, ma non sono né ottimizzate né garantite.
### Profiles
| Filter | Operators | What it matches |
| -------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quickSearch` | `_search` | Searches name, email, phone and short id — the target for simple person or name lookups. |
| `globalSearch` | `_search` | Searches name, headline, summary, position titles, company names, skills, educations and custom field values — the target for skill, role or technology searches. |
| `emailPhoneAndNameSearch` | `_search` | Searches name, email, phone and short id — the target for simple person or name lookups. |
| `fullPositionTitleSkillsAndTagsSearch` | `_search` | Searches name, headline, summary, position titles, company names, skills, educations and custom field values — the target for skill, role or technology searches. |
| `headline` | `_search` | Headline of the profile: the tagline shown under the name on LinkedIn, or the summary line extracted from the CV for profiles that have no LinkedIn page. Editable by hand only when the profile has no LinkedIn identifier. |
| `currentPositions.global` | `_search` | Full-text over title, description |
| `currentPositions.global.title` | `_search` | Job title held at this position, coming from LinkedIn, from a parsed CV or entered by hand. This is the main field behind job title search. |
| `currentPositions.global.description` | `_search` | What the person wrote about this role on their profile or CV. Often empty on imported records. |
| `emails` | `_search` | Email address of the profile or the company it belongs to. Normalized on save: lowercased, and when the input is not a clean address the first valid one found inside it is kept, the row being dropped if there is none. |
| `phones` | `_search` | Phone number search. Only the digits are compared, so a number matches whatever spacing, separators or country prefix it was typed with, and a suffix matches too. |
| `profiles.shortId` | `_search` | Short readable reference for the profile — what users type in quick search and what appears on generated documents. Assigned once at creation and never changes. |
| `historyEntries` | `_search` | Full-text over subject, message |
| `historyEntries.subject` | `_search` | Subject of the interaction: the email subject, the meeting title, or the job offer title. Empty for channels that carry no subject, such as LinkedIn messages, WhatsApp and SMS. |
| `historyEntries.message` | `_search` | Content of the interaction: the email body, the text of the LinkedIn, WhatsApp, SMS or Telegram message, or what the recruiter typed for a manual note. |
| `historyEntries.type` | `_eq`, `_in`, `_is_null` | What kind of interaction this entry records: emails, LinkedIn messages and InMails, connection invitations, SMS, WhatsApp, Telegram, phone calls, scheduled meetings, job applications, or a free note. |
| `historyEntries.subTypeId` | `_eq`, `_in`, `_is_null` | Optional sub-category of the interaction, defined by the group itself — naming its own kinds of notes or calls, for instance. Each sub-category belongs to a single parent type. |
| `historyEntries.occurredAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Single date used to place the interaction on the timeline: the meeting start for a scheduled event, the creation date otherwise. Maintained by a trigger. |
| `historyEntries.userId` | `_eq`, `_in`, `_is_null` | Jarvi user this entry belongs to. Determines the group the entry is attached to, and is used as its sender by default. |
| `historyEntries.hasFiles` | `true`, `false` | The profile has at least one interaction carrying an attached file. |
| `historyEntries.hasRecording` | `true`, `false` | The profile has at least one interaction with a meeting recording. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `location` | `_near` | Geographic search. Use range 0 for an exact city or region match, or a radius in meters. |
| `hasEmail` | `true`, `false` | The profile has at least one email address. |
| `hasEmailType` | `_eq`, `_in`, `_is_null` | The profile has an email address of this type. Takes the same values as the type of an email address. |
| `hasPhone` | `true`, `false` | The profile has at least one phone number. |
| `hasValidPhone` | `true`, `false` | The profile has at least one phone number not marked as invalid. |
| `hasLocation` | `true`, `false` | The profile has at least one location. |
| `hasPhoneType` | `_eq`, `_in`, `_is_null` | The profile has a phone number of this type. Takes the same values as the type of a phone number. |
| `hasWhatsApp` | `true`, `false` | The profile has a phone number reachable on WhatsApp. |
| `isTalent` | `true`, `false` | The profile is a talent — someone you recruit, followed in the ATS. A profile can be a talent and a contact at once. |
| `isContact` | `true`, `false` | The profile is a contact — a client or a hiring manager, followed in the CRM. A profile can be a contact and a talent at once. |
| `hasLinkedin` | `true`, `false` | The profile is matched to a LinkedIn account. |
| `hasResume` | `true`, `false` | The profile has at least one CV attached. |
| `isConnectedOnLinkedinWithMe` | `true`, `false` | The calling user is connected with this profile on LinkedIn, as recorded in the profile's history. |
| `hasReplied` | `true`, `false` | The profile has answered at least once, on any channel. |
| `hasScheduledMessage` | `true`, `false` | A message or a connection invitation is still waiting to be sent to this profile. |
| `hasUnreadMessagesNotifications` | `true`, `false` | The profile has an unread message notification. |
| `firstPositionStartDate` | `_eq`, `_gte`, `_lte`, `_is_null` | Start date of the earliest position on the profile — the usual way to reason about years of experience. |
| `lastPositionStartDate` | `_eq`, `_gte`, `_lte`, `_is_null` | Start date of the current position, that is the one with no end date. |
| `currentPositions.companyId` | `_eq`, `_in`, `_is_null` | The profile currently works at this company. Takes the identifier of the LinkedIn company page, not a Jarvi company id. |
| `projectId` | `_eq`, `_in`, `_is_null` | The profile is associated with this project. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the profile. A parent status also matches profiles carrying any of its children, and the search is narrowed by the project or entity given in the context. Pass "other" for profiles whose status has no parent. |
| `hasProject` | `true`, `false` | The profile is associated with at least one project. |
| `projectCount` | `_eq`, `_gte`, `_lte`, `_is_null` | How many distinct projects the profile is associated with. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `isInCrm` | `true`, `false` | The profile is followed in the CRM, meaning it is a contact. Same as isContact. |
| `isInAts` | `true`, `false` | The profile is followed in the ATS, meaning it is a talent. Same as isTalent. |
| `hasAttachedFiles` | `true`, `false` | The profile has at least one attached file. |
| `isOpenToNewOpportunities` | `true`, `false` | The profile is flagged as open to new opportunities. |
| `isHiring` | `true`, `false` | The profile is flagged as hiring. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the profile, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Technical timestamp of the row creation. For the date the profile entered the base, use addedAt (added\_at) instead. |
| `addedAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the profile entered the group's base — what recruiters read as "added on". Set automatically when the profile is created or restored, unless the caller provides its own value, which is what imports do to keep the original date. |
| `externalId` | `_eq`, `_in` | Identifier of the profile in the client's own system — their ATS, their CRM, or whatever calls the public API. Never a Jarvi identifier. Unique per group, and used to recognise a profile that is sent again. |
| `publicIdentifier` | `_eq`, `_in` | LinkedIn public slug, the part after linkedin.com/in/. Unique per group. The person can change it on LinkedIn, which makes it the least reliable of the LinkedIn identifiers. |
| `linkedinId` | `_eq`, `_in` | Numeric LinkedIn identifier of the person. Unique per group. Not to be confused with memberId (member\_id), which holds the obfuscated "ACoAA…" identifier, nor with publicIdentifier (public\_identifier), which holds the URL slug. |
| `primaryLocale` | `_eq`, `_in`, `_is_null` | Main language of the profile itself — the language its LinkedIn page or CV is written in. Different from the profiles\_locales table, which lists the languages the person speaks and how well. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
### Companies
| Filter | Operators | What it matches |
| ---------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `companySearch` | `_search` | Searches company name and description, LinkedIn tagline and description, legal activity and NAF code, and custom fields — the target for keyword or industry searches. |
| `companyQuickSearch` | `_search` | Searches company name, email and phone — for name lookups only. |
| `companies.global` | `_search` | Company name and description. |
| `location` | `_near` | Geographic search. Use range 0 for an exact city or region match, or a radius in meters. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `hasLinkedinCompanyId` | `true`, `false` | The company is matched to a LinkedIn company page. |
| `hasContacts` | `true`, `false` | The company has at least one profile associated as a contact. |
| `hasProject` | `true`, `false` | The company has at least one project. |
| `isParentCompany` | `true`, `false` | The company has at least one subsidiary. |
| `isSubsidiary` | `true`, `false` | The company has a parent company. |
| `isProtected` | `true`, `false` | The company is under protection, meaning its protection date is still in the future. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the company. A parent status also matches companies carrying any of its children. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the company, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the company record was created. Data imports replace it with the creation date coming from the source ATS or CRM. |
| `externalId` | `_eq`, `_in` | Identifier of the company in the client's own system. Unique per group, and used as the matching key by imports and by the public API. |
| `linkedinCompanyId` | `_eq`, `_in` | Link to the LinkedIn page data of this company. That data is shared across all groups, and a given LinkedIn page can be attached to at most one company per group. |
| `parentId` | `_eq`, `_in`, `_is_null` | Parent company when this record is a subsidiary. Used as a single level: searching on a parent can include its direct subsidiaries, but not their own. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
### Projects
| Filter | Operators | What it matches |
| --------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectNameSearch` | `_search` | Searches project name, its company name and its reference id — the target for project lookups. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `hasJobBoardEnabled` | `true`, `false` | The project is published on at least one job board. |
| `hasLinkedinId` | `true`, `false` | The project is matched to a LinkedIn job posting. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the project. A parent status also matches projects carrying any of its children. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the project, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the project was created. Data imports replace it with the creation date coming from the source ATS or CRM. |
| `externalId` | `_eq`, `_in` | Identifier of the project in the client's own system. Unique per group, and used as the matching key by imports and by the public API. |
| `referenceId` | `_eq`, `_in` | The project reference id is used to map external job applications on website or jobboard to the project. |
| `companyId` | `_eq`, `_in`, `_is_null` | Client company the project is for. Optional — a project can exist without a company, and deleting the company clears the link rather than the project. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
Questi elenchi sono generati dal motore di ricerca stesso, quindi corrispondono sempre a ciò che
l'API accetta. Le stesse definizioni sono servite come JSON Schema da [GET
/openapi](https://functions.prod.jarvi.tech/v1/public-api/openapi), sotto `ProfilesFilters`,
`CompaniesFilters` e `ProjectsFilters`.
## Ordinare
Il parametro `orderBy` di ogni endpoint REST associa un campo e una direzione:
```
?orderBy={"addedAt":"desc"}
```
I campi accettati da una risorsa sono elencati nella documentazione del parametro stesso, sulla pagina dell'endpoint. Ordinare su qualcos'altro ricade sull'ordine predefinito invece di fallire.
## Cercare tramite GraphQL
La query `search` restituisce identificatori e un totale, che carichi poi con una seconda chiamata. Questa divisione in due tempi è ciò che mantiene veloce una ricerca, qualunque sia la dimensione delle schede:
```graphql theme={null}
query FindDevelopers {
search(
resource: "profiles"
filters: { globalSearch: { _search: "javascript AND react" }, isTalent: true }
sort: { field: "addedAt", order: "DESC" }
limit: 50
) {
ids
total
}
}
```
```graphql theme={null}
query LoadThem($ids: [uuid!]!) {
profiles(where: { id: { _in: $ids } }) {
id
firstName
lastName
}
}
```
`resource` vale `profiles`, `companies` o `projects`. `filters` prende lo stesso oggetto del parametro `where` del REST.
## Lasciare `_ilike`
| Invece di | Scrivi |
| --------------------------------------------------------- | --------------------------------------------- |
| `{"lastName": {"_ilike": "%Dupont%"}}` | `{"quickSearch": {"_search": "Dupont"}}` |
| `{"emailAddresses": {"email": {"_ilike": "%@acme.com"}}}` | `{"emails": {"_search": "acme.com"}}` |
| `{"headline": {"_ilike": "%developer%"}}` | `{"globalSearch": {"_search": "developer"}}` |
| `{"name": {"_ilike": "%Acme%"}}` (aziende) | `{"companyQuickSearch": {"_search": "Acme"}}` |
| `{"externalId": {"_ilike": "ABC-123"}}` | `{"externalId": {"_eq": "ABC-123"}}` |
Un identificatore o una URL non è mai una ricerca: usa `_eq`, che passa dal proprio indice.
Ti manca qualcosa, o non trovi un filtro che ti aspettavi? Scrivici — l'elenco qui sopra è guidato
dal motore, aggiungerci un filtro è una modifica piccola.
# Inviare le candidature
Source: https://api-docs.jarvi.tech/it/use-cases/applicants
Questa API ti permetterà di inviare a Jarvi i candidati dal tuo sito, da un modulo o da qualsiasi altra fonte.
Ecco la procedura da seguire:
Se non hai già le tue chiavi API, segui la procedura nella sezione
Ottenere le mie chiavi API
Consulta la documentazione dettagliata dell'API post applicants. Ti sarà sicuramente utile anche capire il concetto di Campi personalizzati
Non sei il primo, stai tranquillo, e troverai probabilmente qui sotto degli esempi di integrazioni
simili a ciò che desideri.
Ecco un esempio di integrazione Wordpress che utilizza il plugin QuForm per i moduli.
```php theme={null}
add_action('quform_post_process_{ID_FORM}', function (array $result, Quform_Form $form){
$file_content = file_get_contents( $this->attachments[ 0 ] );
$file_base_64_content = base64_encode( $file_content );
$data = [
'firstName' => $form->getValue('quform_{ID_FORM}_5'),
'lastName' => $form->getValue('quform_{ID_FORM}_7'),
'fullName' => $form->getValue('quform_{ID_FORM}_6') . ' ' . $form->getValue('quform_{ID_FORM}_7'),
'cb9d4872-bfaa-4c57-b1af-91c008883386' => $form->getValue('quform_{ID_FORM}_10'), // Poste
'17fe3ebf-3641-4a94-ac9f-f11a6d429107' => $form->getValue('quform_{ID_FORM}_31'), // Exp SaaS
'6b751e8d-3947-43f9-af52-1db532210c6d' => $form->getValue('quform_{ID_FORM}_12'), // 1e année CDI
'region' => $form->getValue('quform_{ID_FORM}_15'),
'linkedinUrl' => $form->getValue('quform_{ID_FORM}_17'),
'phoneNumbers' => $form->getValue('quform_{ID_FORM}_20'),
'emailAddresses' => $form->getValue('quform_{ID_FORM}_22'),
'resumesFiles' => [
[
'data' => $file_base_64_content,
'fileName' => $this->attachments[0]->file_name,
],
],
];
wp_remote_post('https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants', [
'body' => json_encode($data),
'headers' => array(
'Content-Type' => 'application/json',
'X-Api-Key' => '{JARVI_PRIVATE_API_KEY}'
),
]);
return $result;
}, 10, 2);
```
Ecco un esempio di integrazione in WebFlow. Questa integrazione è valida anche per un sito web classico.
```javascript theme={null}
document.querySelector('#form-submit-button').addEventListener('click', function() {
var formElement = document.querySelector('.inscription-candidat-form');
var formData = new FormData(formElement);
var formDataAsObject = {}
for (var [key, value] of formData.entries()) {
console.log(key, value);
formDataAsObject[key] = value
}
if(!formDataAsObject.linkedinUrl) console.error("linkedinUrl is mandatory");
const data = {
"referenceId":"PROJECT-REFERENCE-ID",
...formDataAsObject
}
console.log("data to send",data)
fetch('https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants', {
method: 'POST',
mode:'cors',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'JARVI_PUBLIC_API_KEY'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => console.log("data sent",data))
.catch((error) => {
console.error('Error:', error);
});
});
```
Ecco un esempio di integrazione in WebFlow. Questa integrazione è valida anche per un sito web classico.
```javascript theme={null}
// Compila queste costanti con i tuoi valori
const FORM_SELECTOR = "#candidature-form";
const SUBMIT_BUTTON_SELECTOR = "#form-submit-button";
const RESUME_INPUT_NAME = "resumesFiles";
const PROJECT_REFERENCE_ID = "PROJECT-REFERENCE-ID";
const JARVI_PUBLIC_API_KEY = "YOUR JARVI PUBLIC API KEY";
// Funzione di supporto per leggere il file come base64
const readFileAsBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => resolve(event.target.result);
reader.onerror = (error) => reject(error);
reader.readAsDataURL(file);
});
};
// Funzione di supporto per validare i dati del modulo
const validateFormData = (formData) => {
if (!formData.linkedinUrl) {
throw new Error("LinkedIn URL is mandatory");
}
return true;
};
// Gestore principale dell'invio del modulo
document.querySelector(SUBMIT_BUTTON_SELECTOR).addEventListener("click", async function (event) {
event.preventDefault(); // Impedisce l'invio predefinito del modulo
try {
const formElement = document.querySelector(FORM_SELECTOR);
if (!formElement) {
throw new Error("Form element not found");
}
// Converte FormData in oggetto
const formData = new FormData(formElement);
const formDataAsObject = Object.fromEntries(formData.entries());
// Valida i dati del modulo
validateFormData(formDataAsObject);
// Ottiene e valida il file
const fileInput = formElement.querySelector(`input[name="${RESUME_INPUT_NAME}"]`);
if (!fileInput) {
throw new Error("Resume input field not found");
}
const file = fileInput.files[0];
if (!file) {
throw new Error("No file uploaded");
}
// Legge il file e prepara i dati
const base64Data = await readFileAsBase64(file);
const resumesFiles = [
{
fileName: file.name,
data: base64Data,
},
];
const data = {
referenceId: "PROJECT-REFERENCE-ID", // Aggiorna con il tuo ID di riferimento del progetto
...formDataAsObject,
resumesFiles,
};
console.log("Sending data:", data);
// Invia dati all'API
const response = await fetch("https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
"X-Api-Key": JARVI_PUBLIC_API_KEY,
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
console.log("Data sent successfully:", responseData);
// Opzionale: Mostra un messaggio di successo all'utente
alert("Application submitted successfully!");
} catch (error) {
console.error("Error:", error);
alert(`Error: ${error.message}`);
}
});
```
# Recuperare i progetti
Source: https://api-docs.jarvi.tech/it/use-cases/projects
Questa API ti permetterà di recuperare tutti i progetti di recruiting ma anche di prospezione e tutte le informazioni associate. L'obiettivo è spesso quello di mostrarli sul tuo sito web.
Ecco come procedere:
Se non hai già le tue chiavi API, segui la procedura nella sezione
Ottenere le mie chiavi API
Consulta la documentazione dettagliata dell'API GET projects. Ti sarà
sicuramente utile anche capire il concetto di Campi personalizzati
Hai recuperato i progetti, forse desideri anche inviare le candidature su Jarvi:
Caso d'uso: inviare le candidature in Jarvi
# Mijn API-sleutels verkrijgen
Source: https://api-docs.jarvi.tech/nl/api-keys
Je API-sleutels geven je toegang tot de API
Ga naar je voorkeuren en vervolgens naar API. Klik dan op de knop om API-sleutels te genereren zoals hieronder aangegeven.
Je krijgt dan 2 sleutels:
* Een **publieke sleutel**, waarmee je alleen nieuwe profielen en sollicitaties kunt aanmaken.
* Een **privésleutel**, waarmee je alles kunt doen. Deze sleutel wordt slechts één keer getoond, bij het aanmaken. Zorg er dus voor dat je deze op een veilige plaats bewaart.
De privésleutel mag niet worden gedeeld of in javascript-code staan die toegankelijk is voor de gebruiker.
# [Sollicitatie] Aanmaken of bijwerken
Source: https://api-docs.jarvi.tech/nl/api-reference/applicants/post
POST /rest/v2/applicants
Save a new job application in Jarvi. It creates the profile if it does not exist, upload the file, link the profile to the recruitment project,...
Je kunt je **publieke api-sleutel** gebruiken voor deze aanvraag. Hierdoor kun je het direct in de javascript-code van
de site doen.
De maximale grootte van de aanvraag is 4.5MB. We raden je aan om de grootte van je CV-uploadveld te beperken tot een
kleinere omvang.
Je kunt alles opslaan op het profiel dankzij Aangepaste velden. Klik op deze link om te begrijpen hoe je informatie kunt lezen en schrijven in aangepaste velden.
# [Bedrijven] Lijst weergeven
Source: https://api-docs.jarvi.tech/nl/api-reference/companies/get
GET /rest/v2/companies
Get all companies (recruitment, sales, etc.) with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Je moet je **privé api-sleutel** gebruiken voor dit verzoek.
Er zijn wijzigingen gepland voor deze API in de zomer van 2025. Het is daarom mogelijk dat er aanpassingen aan jouw
kant nodig zijn als gevolg van deze ontwikkelingen.
Je kunt alle informatie van elk profiel volledig aanpassen dankzij Aangepaste velden. Klik op deze link om te begrijpen hoe je informatie in aangepaste velden kunt lezen en schrijven.
# [Bedrijf] Aanmaken of bijwerken
Source: https://api-docs.jarvi.tech/nl/api-reference/companies/post
POST /rest/v2/companies
Save a company in Jarvi. It creates the company if it does not exist, updates it if it does.
Je moet je **privé api-sleutel** gebruiken voor dit verzoek.
Je kunt alle informatie van elk profiel volledig aanpassen met behulp van Aangepaste velden. Klik op deze link om te begrijpen hoe je informatie in aangepaste velden kunt lezen en schrijven.
# GraphQL
Source: https://api-docs.jarvi.tech/nl/api-reference/graphql
POST /graphql
De complete API om al het overige te doen!
Deze API stelt je in staat om absoluut alles te doen wat je maar wilt. Je kunt je verzoeken aanpassen om te lezen/schrijven wat je nodig hebt.
Wij garanderen niet dat de informatie die beschikbaar is in deze API niet zonder voorafgaande kennisgeving kan
veranderen. We raden je aan deze API niet te gebruiken voor kritieke toepassingen zonder eerst met ons te overleggen.
De editor biedt je een interface met alle mogelijkheden en stelt je in staat om je queries direct te testen.
Hier zie je in een video hoe je de query-editor kunt gebruiken:
# [Vacatures] Lijst weergeven
Source: https://api-docs.jarvi.tech/nl/api-reference/offers/get
GET /rest/v2/offers
Get all offers with their custom fields, files, and more.
Je moet je **publieke api-sleutel** gebruiken voor deze aanvraag. Je moet de gegevens aan jouw kant cachen om binnen
de dagelijkse limieten voor het aantal aanroepen te blijven.
Je kunt alle informatie van elk project volledig aanpassen met behulp van Aangepaste velden. Klik op deze link om te begrijpen hoe je informatie in aangepaste velden kunt lezen en schrijven.
# [Profielen] Weergeven
Source: https://api-docs.jarvi.tech/nl/api-reference/profiles/get
GET /rest/v2/profiles
Get all profiles with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Je moet je **privé api-sleutel** gebruiken voor dit verzoek.
Je kunt alle informatie van elk profiel volledig aanpassen met behulp van Aangepaste velden. Klik op deze link om te begrijpen hoe je informatie in aangepaste velden kunt lezen en schrijven.
# [Profielen] Aanmaken of bijwerken
Source: https://api-docs.jarvi.tech/nl/api-reference/profiles/post
POST /rest/v2/profiles
Save a profile in Jarvi. It creates the profile if it does not exist, updates it if it does.
Je moet je **privé api-sleutel** gebruiken voor dit verzoek.
Je kunt alle informatie van elk profiel volledig aanpassen met behulp van Aangepaste velden. Klik op deze link om te begrijpen hoe je informatie in aangepaste velden kunt lezen en schrijven.
# [Projecten] Weergeven
Source: https://api-docs.jarvi.tech/nl/api-reference/projects/get
GET /rest/v2/projects
Get all projects (recruitment, sales, etc.) with their custom fields, files, and more. This endpoint is not paginated, you got enverything at once.
Je moet je **privé api-sleutel** gebruiken voor dit verzoek. Je moet de gegevens aan jouw kant cachen om de dagelijkse
limieten voor het aantal aanroepen te respecteren.
Je kunt alle informatie van elk project volledig aanpassen met behulp van Aangepaste velden. Klik op deze link om te begrijpen hoe je informatie in aangepaste velden kunt lezen en schrijven.
# [Projecten] Aanmaken of bijwerken
Source: https://api-docs.jarvi.tech/nl/api-reference/projects/post
POST /rest/v2/projects
Save a project in Jarvi. It creates the project if it does not exist, updates it if it does.
Je moet je **privé api-sleutel** gebruiken voor dit verzoek.
Je kunt alle informatie van elk profiel volledig aanpassen met behulp van Aangepaste velden. Klik op deze link om te begrijpen hoe je informatie in aangepaste velden kunt lezen en schrijven.
# Aangepaste velden
Source: https://api-docs.jarvi.tech/nl/custom-fields
Inzicht in het concept van aangepaste velden stelt je in staat om het volledige potentieel van Jarvi en deze API te benutten
Jarvi-gebruikers kunnen de beschikbare invoervelden instellen op de
* profielen (kandidaten of prospects)
* projecten (werving of prospectie)
* bedrijven
Elk veld wordt geïdentificeerd door een unieke ID (UUID).
Volg deze link om toegang te krijgen tot Jarvi en een lijst te krijgen van alle beschikbare invoervelden voor het
recruitmentbureau.
Met de API kunt u informatie uit deze velden ophalen en ook wijzigen.
U hoeft hiervoor alleen de unieke id (UUID) te kennen.
Door op onderstaande link te klikken en vervolgens een veld te openen, vindt u de id aan het einde van de url.
Bijvoorbeeld, `https://app.jarvi.tech/#/settings/fields/cdccbea3-debd-453d-8a30-691f48c5a9e5` de id is hier `cdccbea3-debd-453d-8a30-691f48c5a9e5`.
# Introductie
Source: https://api-docs.jarvi.tech/nl/introduction
Welkom bij de documentatie van de Jarvi API
## Waarom de Jarvi API gebruiken?
Deze API stelt je in staat om absoluut alles te doen wat je wilt. Dezelfde API wordt gebruikt door de Jarvi-applicatie en zijn extensie.
Hier zijn de meest voorkomende gebruikssituaties:
Stuur sollicitaties van je website, formulieren of andere bronnen rechtstreeks naar Jarvi.
Haal projecten, vacatures en alle bijbehorende informatie op die in Jarvi zijn aangemaakt.
Maar vergeet niet, alles is mogelijk.
## Gebruiksvoorwaarden
* De Jarvi API hanteert limieten op basis van de **cumulatieve uitvoeringstijd** van je verzoeken, niet op het aantal.
* Deze limieten gelden **alleen van maandag tot en met vrijdag, van 07:00 tot 18:00 UTC** (06:00–17:00 UTC tijdens de zomertijd). Daarbuiten — 's nachts en in het weekend — geldt geen enkele limiet: dat is het ideale moment voor je batchverwerking.
* Tijdens kantooruren is je gebruik beperkt tot **5 minuten** cumulatieve uitvoeringstijd per uur.
* Deze limieten zijn zeer ruim bemeten en vertegenwoordigen ongeveer **100 keer het gemiddelde gebruik** van een Jarvi-gebruiker.
* Bij overschrijding retourneert de API een `429` fout met details over je verbruik, samen met een `Retry-After` header.
* Als je een hogere limiet nodig hebt, neem contact op met onze support — we kunnen de drempels aanpassen aan jouw gebruik.
* Je verbindt je ertoe de regelgeving met betrekking tot de bescherming van privégegevens te respecteren, in het bijzonder de AVG, voor alle gegevens die via de API worden gelezen/geschreven.
## Snelle start
Je API-sleutels geven je toegang tot de API
# MCP (AI-assistenten)
Source: https://api-docs.jarvi.tech/nl/mcp
Verbind AI-assistenten zoals Claude Desktop of Cursor met je Jarvi-gegevens
## Wat is MCP?
Het [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is een open standaard waarmee AI-assistenten verbinding kunnen maken met externe tools en gegevensbronnen. Jarvi biedt een MCP-server zodat je AI-assistenten kunt gebruiken om je Jarvi-gegevens direct te doorzoeken, te lezen en bij te werken.
## Compatibele clients
Elke MCP-compatibele client kan verbinding maken met Jarvi, waaronder:
* [Claude Desktop](https://claude.ai/download)
* [Cursor](https://cursor.com)
* [Windsurf](https://codeium.com/windsurf)
* Elke client die **Streamable HTTP** transport ondersteunt
## Configuratie
### Claude Desktop
Ga naar **Instellingen > Connectoren > Voeg een aangepaste connector toe**, voer een naam in (bijv. "Jarvi") en de URL:
```
https://mcp.jarvi.tech
```
Claude Desktop regelt de authenticatie automatisch via OAuth2 — je wordt doorgestuurd om in te loggen met je Jarvi-account.
### Andere clients
Voor clients die OAuth2 niet ondersteunen, gebruik je een privé API-sleutel:
* **Transport**: Streamable HTTP
* **URL**: `https://mcp.jarvi.tech`
* **Authenticatie**: header `x-api-key` met je [privé API-sleutel](./api-keys)
Je privésleutel geeft volledige toegang tot je Jarvi-gegevens. Deel deze nooit publiekelijk.
## Beschikbare tools
Zodra je verbonden bent, zijn de volgende tools beschikbaar voor je AI-assistent:
| Tool | Beschrijving |
| ------------------------- | --------------------------------------------------------------------------------------------- |
| `whoAmI` | Geeft de identiteit van de ingelogde gebruiker terug (naam, e-mail, workspace) |
| `getStatuses` | Toon beschikbare statussen voor profielen, bedrijven of projecten |
| `getCustomFields` | Toon aangepaste velden voor een bepaald entiteitstype |
| `getHistorySubtypes` | Toon aangepaste subtypes voor interacties (bijv. categorieën van gesprekken) |
| `advancedSearch` | Zoek profielen, bedrijven of projecten in natuurlijke taal |
| `getEntityData` | Haal gedetailleerde gegevens op van profielen, bedrijven of projecten op basis van ID's |
| `getAgenda` | Haal aankomende gebeurtenissen op uit gekoppelde agenda's |
| `getNotifications` | Haal recente notificaties van de gebruiker op |
| `createProfile` | Maak een nieuw profiel aan (talent en/of contactpersoon) |
| `createCompany` | Maak een nieuw bedrijf aan |
| `createProject` | Maak een nieuw project / wervingsopdracht aan |
| `associateProfiles` | Koppel een profiel aan een project (talent of contact) of aan een bedrijf |
| `createHistoryEntry` | Voeg een notitie of gesprekslog toe aan de geschiedenis van een profiel |
| `createTodo` | Maak een herinnering/taak aan gekoppeld aan een profiel, bedrijf of project |
| `sendMessage` | Verstuur berichten naar één of meerdere profielen (e-mail, LinkedIn, WhatsApp, Telegram, SMS) |
| `getMessageTemplates` | Toon beschikbare berichtsjablonen (persoonlijk en gedeeld door collega's) |
| `uploadFile` | Upload een bestand en koppel het aan een entiteit (profiel, bedrijf of project) |
| `cancelScheduledMessages` | Annuleer één of meerdere geplande berichten |
| `updateProfile` | Werk een profiel bij, voeg e-mailadressen of telefoonnummers toe |
| `updateCompany` | Werk een bedrijf bij, wijzig de status op naam, voeg e-mailadressen toe |
| `updateProject` | Werk een project bij, wijzig de status op naam, koppel een bedrijf |
| `fillJobOffer` | Genereer of verfijn een vacature op basis van projectgegevens |
| `getOutboxDiagnostic` | Diagnosticeer de outbox: toon wachtende geplande berichten met status en fouten |
| `retryOutboxTasks` | Probeer mislukte taken opnieuw door hun status te resetten |
| `queryGraphQL` | Voer GraphQL queries/mutaties uit op toegestane tabellen (jouw rechten zijn van toepassing) |
Alle handelingen worden uitgevoerd met **jouw rechten**. Beveiliging op rijniveau is actief — je hebt alleen toegang tot gegevens waartoe jouw account toegang heeft.
## Voorbeelden van prompts
Zodra je verbonden bent, probeer je AI-assistent te vragen:
* *"Wie ben ik?"*
* *"Zoek React-ontwikkelaars in Parijs"*
* *"Laat me de details van project X zien"*
* *"Maak een profiel aan voor Jean Dupont, ontwikkelaar"*
* *"Plaats deze kandidaat op project X"*
* *"Zet de status van bedrijf Acme op Gekwalificeerd"*
* *"Welke aangepaste velden bestaan er op profielen?"*
* *"Noteer een gesprek met Jean Dupont: hebben zijn beschikbaarheid besproken"*
* *"Herinner me eraan om Acme volgende week maandag te hercontacteren"*
* *"Update de functietitel van Jean Dupont naar Senior Ontwikkelaar"*
## Beperkingen
* Dezelfde [gebruikslimieten](./introduction#gebruiksvoorwaarden) zijn van toepassing, gerekend op cumulatieve uitvoeringstijd. Heb je meer nodig, neem dan contact op met onze support.
* Verwijderoperaties zijn geblokkeerd — gebruik in plaats daarvan zachte verwijdering (update van `deletedAt`).
* Te grote antwoorden worden automatisch ingekort. Gebruik `limit`-argumenten in je verzoeken.
* Destructieve tools (mutaties, campagne lanceren, berichten opnieuw versturen) vereisen bevestiging van de AI-assistent voordat ze worden uitgevoerd.
# Zoeken
Source: https://api-docs.jarvi.tech/nl/search
Profielen, bedrijven en projecten filteren op geïndexeerde velden met de operator _search
## Waarom `_search`
Jarvi draait een full-text zoekmachine over je gegevens. Namen, e-mailadressen, telefoonnummers, functietitels, vaardigheden en waarden van eigen velden zijn geïndexeerd, en `_search` bevraagt die indexen.
Filteren met `_ilike "%term%"` doet iets heel anders: PostgreSQL loopt elk record in je werkruimte langs en vergelijkt het met het patroon. Op een productiebasis werd één enkele `_ilike` op een achternaam gemeten op 2,4 seconden; dezelfde zoekopdracht via de index antwoordt in enkele tientallen milliseconden.
`_ilike`, `_like`, `_nilike` en `_nlike` verdwijnen uit de publieke API op **31 juli 2027**.
Op de REST-endpoints hoef je niets te doen — `_ilike`-filters worden al voor je vertaald naar een
geïndexeerde zoekopdracht, en het antwoord vertelt welk filter het jouwe verving. Via het
`/graphql`-endpoint is de query van jou: die kan elke tabel aanspreken, dus je past hem zelf aan.
## De operator gebruiken
Een filter neemt de naam van een zoekdoel en een term:
```json theme={null}
{ "quickSearch": { "_search": "Dupont" } }
```
Combineer er meerdere met `_and`, `_or` en `_not`, zoals bij elk ander filter:
```json theme={null}
{
"_and": [
{ "globalSearch": { "_search": "javascript" } },
{ "isTalent": true },
{ "addedAt": { "_gte": "2026-01-01" } }
]
}
```
### Querysyntaxis
De term is zelf een query, geen gewone tekenreeks:
| Syntaxis | Komt overeen met |
| -------------------------------- | ------------------------------------------------------------------------ |
| `developer designer` | een van beide woorden |
| `developer AND designer` | beide woorden |
| `developer AND (java OR kotlin)` | beide, met een van beide technologieën |
| `NOT intern` | records zonder dat woord |
| `"project manager"` | de exacte zinsnede, in die volgorde |
| `java^10 python` | beide woorden, waarbij java tien keer zwaarder weegt in de rangschikking |
### Hele woorden, geen fragmenten
Namen worden woord voor woord geïndexeerd, vanaf het begin: `Dupo` vindt *Dupont*, `upont` niet. Hetzelfde geldt voor bedrijfs- en projectnamen.
E-mailadressen en telefoonnummers zijn anders geïndexeerd en accepteren wél een fragment op elke plek: een nummer vind je via de laatste cijfers, een e-mailadres via het domein.
## De filters, per resource
Alle onderstaande filters steunen op een index. Andere Hasura-kolommen en -relaties worden nog steeds geaccepteerd, maar zijn niet geoptimaliseerd en niet gegarandeerd.
### Profiles
| Filter | Operators | What it matches |
| -------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `quickSearch` | `_search` | Searches name, email, phone and short id — the target for simple person or name lookups. |
| `globalSearch` | `_search` | Searches name, headline, summary, position titles, company names, skills, educations and custom field values — the target for skill, role or technology searches. |
| `emailPhoneAndNameSearch` | `_search` | Searches name, email, phone and short id — the target for simple person or name lookups. |
| `fullPositionTitleSkillsAndTagsSearch` | `_search` | Searches name, headline, summary, position titles, company names, skills, educations and custom field values — the target for skill, role or technology searches. |
| `headline` | `_search` | Headline of the profile: the tagline shown under the name on LinkedIn, or the summary line extracted from the CV for profiles that have no LinkedIn page. Editable by hand only when the profile has no LinkedIn identifier. |
| `currentPositions.global` | `_search` | Full-text over title, description |
| `currentPositions.global.title` | `_search` | Job title held at this position, coming from LinkedIn, from a parsed CV or entered by hand. This is the main field behind job title search. |
| `currentPositions.global.description` | `_search` | What the person wrote about this role on their profile or CV. Often empty on imported records. |
| `emails` | `_search` | Email address of the profile or the company it belongs to. Normalized on save: lowercased, and when the input is not a clean address the first valid one found inside it is kept, the row being dropped if there is none. |
| `phones` | `_search` | Phone number search. Only the digits are compared, so a number matches whatever spacing, separators or country prefix it was typed with, and a suffix matches too. |
| `profiles.shortId` | `_search` | Short readable reference for the profile — what users type in quick search and what appears on generated documents. Assigned once at creation and never changes. |
| `historyEntries` | `_search` | Full-text over subject, message |
| `historyEntries.subject` | `_search` | Subject of the interaction: the email subject, the meeting title, or the job offer title. Empty for channels that carry no subject, such as LinkedIn messages, WhatsApp and SMS. |
| `historyEntries.message` | `_search` | Content of the interaction: the email body, the text of the LinkedIn, WhatsApp, SMS or Telegram message, or what the recruiter typed for a manual note. |
| `historyEntries.type` | `_eq`, `_in`, `_is_null` | What kind of interaction this entry records: emails, LinkedIn messages and InMails, connection invitations, SMS, WhatsApp, Telegram, phone calls, scheduled meetings, job applications, or a free note. |
| `historyEntries.subTypeId` | `_eq`, `_in`, `_is_null` | Optional sub-category of the interaction, defined by the group itself — naming its own kinds of notes or calls, for instance. Each sub-category belongs to a single parent type. |
| `historyEntries.occurredAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Single date used to place the interaction on the timeline: the meeting start for a scheduled event, the creation date otherwise. Maintained by a trigger. |
| `historyEntries.userId` | `_eq`, `_in`, `_is_null` | Jarvi user this entry belongs to. Determines the group the entry is attached to, and is used as its sender by default. |
| `historyEntries.hasFiles` | `true`, `false` | The profile has at least one interaction carrying an attached file. |
| `historyEntries.hasRecording` | `true`, `false` | The profile has at least one interaction with a meeting recording. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `location` | `_near` | Geographic search. Use range 0 for an exact city or region match, or a radius in meters. |
| `hasEmail` | `true`, `false` | The profile has at least one email address. |
| `hasEmailType` | `_eq`, `_in`, `_is_null` | The profile has an email address of this type. Takes the same values as the type of an email address. |
| `hasPhone` | `true`, `false` | The profile has at least one phone number. |
| `hasValidPhone` | `true`, `false` | The profile has at least one phone number not marked as invalid. |
| `hasLocation` | `true`, `false` | The profile has at least one location. |
| `hasPhoneType` | `_eq`, `_in`, `_is_null` | The profile has a phone number of this type. Takes the same values as the type of a phone number. |
| `hasWhatsApp` | `true`, `false` | The profile has a phone number reachable on WhatsApp. |
| `isTalent` | `true`, `false` | The profile is a talent — someone you recruit, followed in the ATS. A profile can be a talent and a contact at once. |
| `isContact` | `true`, `false` | The profile is a contact — a client or a hiring manager, followed in the CRM. A profile can be a contact and a talent at once. |
| `hasLinkedin` | `true`, `false` | The profile is matched to a LinkedIn account. |
| `hasResume` | `true`, `false` | The profile has at least one CV attached. |
| `isConnectedOnLinkedinWithMe` | `true`, `false` | The calling user is connected with this profile on LinkedIn, as recorded in the profile's history. |
| `hasReplied` | `true`, `false` | The profile has answered at least once, on any channel. |
| `hasScheduledMessage` | `true`, `false` | A message or a connection invitation is still waiting to be sent to this profile. |
| `hasUnreadMessagesNotifications` | `true`, `false` | The profile has an unread message notification. |
| `firstPositionStartDate` | `_eq`, `_gte`, `_lte`, `_is_null` | Start date of the earliest position on the profile — the usual way to reason about years of experience. |
| `lastPositionStartDate` | `_eq`, `_gte`, `_lte`, `_is_null` | Start date of the current position, that is the one with no end date. |
| `currentPositions.companyId` | `_eq`, `_in`, `_is_null` | The profile currently works at this company. Takes the identifier of the LinkedIn company page, not a Jarvi company id. |
| `projectId` | `_eq`, `_in`, `_is_null` | The profile is associated with this project. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the profile. A parent status also matches profiles carrying any of its children, and the search is narrowed by the project or entity given in the context. Pass "other" for profiles whose status has no parent. |
| `hasProject` | `true`, `false` | The profile is associated with at least one project. |
| `projectCount` | `_eq`, `_gte`, `_lte`, `_is_null` | How many distinct projects the profile is associated with. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `isInCrm` | `true`, `false` | The profile is followed in the CRM, meaning it is a contact. Same as isContact. |
| `isInAts` | `true`, `false` | The profile is followed in the ATS, meaning it is a talent. Same as isTalent. |
| `hasAttachedFiles` | `true`, `false` | The profile has at least one attached file. |
| `isOpenToNewOpportunities` | `true`, `false` | The profile is flagged as open to new opportunities. |
| `isHiring` | `true`, `false` | The profile is flagged as hiring. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the profile, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Technical timestamp of the row creation. For the date the profile entered the base, use addedAt (added\_at) instead. |
| `addedAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the profile entered the group's base — what recruiters read as "added on". Set automatically when the profile is created or restored, unless the caller provides its own value, which is what imports do to keep the original date. |
| `externalId` | `_eq`, `_in` | Identifier of the profile in the client's own system — their ATS, their CRM, or whatever calls the public API. Never a Jarvi identifier. Unique per group, and used to recognise a profile that is sent again. |
| `publicIdentifier` | `_eq`, `_in` | LinkedIn public slug, the part after linkedin.com/in/. Unique per group. The person can change it on LinkedIn, which makes it the least reliable of the LinkedIn identifiers. |
| `linkedinId` | `_eq`, `_in` | Numeric LinkedIn identifier of the person. Unique per group. Not to be confused with memberId (member\_id), which holds the obfuscated "ACoAA…" identifier, nor with publicIdentifier (public\_identifier), which holds the URL slug. |
| `primaryLocale` | `_eq`, `_in`, `_is_null` | Main language of the profile itself — the language its LinkedIn page or CV is written in. Different from the profiles\_locales table, which lists the languages the person speaks and how well. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
### Companies
| Filter | Operators | What it matches |
| ---------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `companySearch` | `_search` | Searches company name and description, LinkedIn tagline and description, legal activity and NAF code, and custom fields — the target for keyword or industry searches. |
| `companyQuickSearch` | `_search` | Searches company name, email and phone — for name lookups only. |
| `companies.global` | `_search` | Company name and description. |
| `location` | `_near` | Geographic search. Use range 0 for an exact city or region match, or a radius in meters. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `hasLinkedinCompanyId` | `true`, `false` | The company is matched to a LinkedIn company page. |
| `hasContacts` | `true`, `false` | The company has at least one profile associated as a contact. |
| `hasProject` | `true`, `false` | The company has at least one project. |
| `isParentCompany` | `true`, `false` | The company has at least one subsidiary. |
| `isSubsidiary` | `true`, `false` | The company has a parent company. |
| `isProtected` | `true`, `false` | The company is under protection, meaning its protection date is still in the future. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the company. A parent status also matches companies carrying any of its children. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the company, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the company record was created. Data imports replace it with the creation date coming from the source ATS or CRM. |
| `externalId` | `_eq`, `_in` | Identifier of the company in the client's own system. Unique per group, and used as the matching key by imports and by the public API. |
| `linkedinCompanyId` | `_eq`, `_in` | Link to the LinkedIn page data of this company. That data is shared across all groups, and a given LinkedIn page can be attached to at most one company per group. |
| `parentId` | `_eq`, `_in`, `_is_null` | Parent company when this record is a subsidiary. Used as a single level: searching on a parent can include its direct subsidiaries, but not their own. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
### Projects
| Filter | Operators | What it matches |
| --------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `projectNameSearch` | `_search` | Searches project name, its company name and its reference id — the target for project lookups. |
| `customField` | `_search` | Value of a custom field. Target one field by suffixing its id, as in "customField.\". |
| `hasJobBoardEnabled` | `true`, `false` | The project is published on at least one job board. |
| `hasLinkedinId` | `true`, `false` | The project is matched to a LinkedIn job posting. |
| `filledCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `emptyCustomFields` | `_all`, `_in` | Custom field UUIDs. \_all requires every field, \_in requires any of them. |
| `statusId` | `_eq`, `_in`, `_is_null` | Status of the project. A parent status also matches projects carrying any of its children. |
| `id` | `_eq`, `_in`, `_is_null` | Jarvi identifier of the project, used in the public API and in app URLs. |
| `createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | Date the project was created. Data imports replace it with the creation date coming from the source ATS or CRM. |
| `externalId` | `_eq`, `_in` | Identifier of the project in the client's own system. Unique per group, and used as the matching key by imports and by the public API. |
| `referenceId` | `_eq`, `_in` | The project reference id is used to map external job applications on website or jobboard to the project. |
| `companyId` | `_eq`, `_in`, `_is_null` | Client company the project is for. Optional — a project can exist without a company, and deleting the company clears the link rather than the project. |
| `auditLogs.createdAt` | `_eq`, `_gte`, `_lte`, `_is_null` | When the tracked change happened. This is the reference date for incremental synchronisation: fetching everything modified since a given date goes through this column. |
Deze lijsten worden gegenereerd door de zoekmachine zelf en komen dus altijd overeen met wat de API
accepteert. Dezelfde definities worden als JSON Schema geserveerd door [GET
/openapi](https://functions.prod.jarvi.tech/v1/public-api/openapi), onder `ProfilesFilters`,
`CompaniesFilters` en `ProjectsFilters`.
## Sorteren
De parameter `orderBy` van elk REST-endpoint koppelt één veld aan een richting:
```
?orderBy={"addedAt":"desc"}
```
Welke velden een resource accepteert staat in de documentatie van de parameter zelf, op de pagina van het endpoint. Sorteren op iets anders valt terug op de standaardvolgorde in plaats van te mislukken.
## Zoeken via GraphQL
De query `search` geeft identificatoren en een totaal terug, die je daarna in een tweede aanroep ophaalt. Die opsplitsing in twee stappen houdt een zoekopdracht snel, hoe groot de records ook zijn:
```graphql theme={null}
query FindDevelopers {
search(
resource: "profiles"
filters: { globalSearch: { _search: "javascript AND react" }, isTalent: true }
sort: { field: "addedAt", order: "DESC" }
limit: 50
) {
ids
total
}
}
```
```graphql theme={null}
query LoadThem($ids: [uuid!]!) {
profiles(where: { id: { _in: $ids } }) {
id
firstName
lastName
}
}
```
`resource` is `profiles`, `companies` of `projects`. `filters` neemt hetzelfde object als de REST-parameter `where`.
## Weg van `_ilike`
| In plaats van | Schrijf |
| --------------------------------------------------------- | --------------------------------------------- |
| `{"lastName": {"_ilike": "%Dupont%"}}` | `{"quickSearch": {"_search": "Dupont"}}` |
| `{"emailAddresses": {"email": {"_ilike": "%@acme.com"}}}` | `{"emails": {"_search": "acme.com"}}` |
| `{"headline": {"_ilike": "%developer%"}}` | `{"globalSearch": {"_search": "developer"}}` |
| `{"name": {"_ilike": "%Acme%"}}` (bedrijven) | `{"companyQuickSearch": {"_search": "Acme"}}` |
| `{"externalId": {"_ilike": "ABC-123"}}` | `{"externalId": {"_eq": "ABC-123"}}` |
Een identificator of een URL is nooit een zoekopdracht: gebruik `_eq`, dat zijn eigen index gebruikt.
Mis je iets, of vind je een filter niet dat je verwachtte? Laat het ons weten — de lijst hierboven
wordt door de motor aangestuurd, er een filter aan toevoegen is een kleine wijziging.
# Sollicitaties versturen
Source: https://api-docs.jarvi.tech/nl/use-cases/applicants
Met deze API kunt u kandidaten van uw website, een formulier of een andere bron naar Jarvi sturen.
Hier is hoe je te werk moet gaan:
Als je nog geen API-sleutels hebt, volg dan de stappen in de sectie
Mijn API-sleutels verkrijgen
Raadpleeg de gedetailleerde documentatie van de API post applicants. Het zal ook nuttig zijn om het concept van Aangepaste velden te begrijpen
Je bent zeker niet de eerste, wees gerust, en hieronder vind je waarschijnlijk voorbeelden van integraties
die vergelijkbaar zijn met wat je zoekt.
Hier is een voorbeeld van een Wordpress-integratie met behulp van de QuForm-plugin voor formulieren.
```php theme={null}
add_action('quform_post_process_{ID_FORM}', function (array $result, Quform_Form $form){
$file_content = file_get_contents( $this->attachments[ 0 ] );
$file_base_64_content = base64_encode( $file_content );
$data = [
'firstName' => $form->getValue('quform_{ID_FORM}_5'),
'lastName' => $form->getValue('quform_{ID_FORM}_7'),
'fullName' => $form->getValue('quform_{ID_FORM}_6') . ' ' . $form->getValue('quform_{ID_FORM}_7'),
'cb9d4872-bfaa-4c57-b1af-91c008883386' => $form->getValue('quform_{ID_FORM}_10'), // Poste
'17fe3ebf-3641-4a94-ac9f-f11a6d429107' => $form->getValue('quform_{ID_FORM}_31'), // Exp SaaS
'6b751e8d-3947-43f9-af52-1db532210c6d' => $form->getValue('quform_{ID_FORM}_12'), // 1e année CDI
'region' => $form->getValue('quform_{ID_FORM}_15'),
'linkedinUrl' => $form->getValue('quform_{ID_FORM}_17'),
'phoneNumbers' => $form->getValue('quform_{ID_FORM}_20'),
'emailAddresses' => $form->getValue('quform_{ID_FORM}_22'),
'resumesFiles' => [
[
'data' => $file_base_64_content,
'fileName' => $this->attachments[0]->file_name,
],
],
];
wp_remote_post('https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants', [
'body' => json_encode($data),
'headers' => array(
'Content-Type' => 'application/json',
'X-Api-Key' => '{JARVI_PRIVATE_API_KEY}'
),
]);
return $result;
}, 10, 2);
```
Hier is een voorbeeld van een integratie in WebFlow. Deze integratie werkt ook voor een klassieke website.
```javascript theme={null}
document.querySelector('#form-submit-button').addEventListener('click', function() {
var formElement = document.querySelector('.inscription-candidat-form');
var formData = new FormData(formElement);
var formDataAsObject = {}
for (var [key, value] of formData.entries()) {
console.log(key, value);
formDataAsObject[key] = value
}
if(!formDataAsObject.linkedinUrl) console.error("linkedinUrl is mandatory");
const data = {
"referenceId":"PROJECT-REFERENCE-ID",
...formDataAsObject
}
console.log("data to send",data)
fetch('https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants', {
method: 'POST',
mode:'cors',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': 'JARVI_PUBLIC_API_KEY'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => console.log("data sent",data))
.catch((error) => {
console.error('Error:', error);
});
});
```
Hier is een voorbeeld van een integratie in WebFlow. Deze integratie werkt ook voor een klassieke website.
```javascript theme={null}
// Vul deze constanten in met je eigen waarden
const FORM_SELECTOR = "#candidature-form";
const SUBMIT_BUTTON_SELECTOR = "#form-submit-button";
const RESUME_INPUT_NAME = "resumesFiles";
const PROJECT_REFERENCE_ID = "PROJECT-REFERENCE-ID";
const JARVI_PUBLIC_API_KEY = "YOUR JARVI PUBLIC API KEY";
// Hulpfunctie om bestand als base64 te lezen
const readFileAsBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => resolve(event.target.result);
reader.onerror = (error) => reject(error);
reader.readAsDataURL(file);
});
};
// Hulpfunctie om formuliergegevens te valideren
const validateFormData = (formData) => {
if (!formData.linkedinUrl) {
throw new Error("LinkedIn URL is verplicht");
}
return true;
};
// Hoofdfunctie voor formulierindiening
document.querySelector(SUBMIT_BUTTON_SELECTOR).addEventListener("click", async function (event) {
event.preventDefault(); // Voorkom standaard formulierindiening
try {
const formElement = document.querySelector(FORM_SELECTOR);
if (!formElement) {
throw new Error("Formulierelement niet gevonden");
}
// Converteer FormData naar object
const formData = new FormData(formElement);
const formDataAsObject = Object.fromEntries(formData.entries());
// Valideer formuliergegevens
validateFormData(formDataAsObject);
// Haal bestand op en valideer
const fileInput = formElement.querySelector(`input[name="${RESUME_INPUT_NAME}"]`);
if (!fileInput) {
throw new Error("CV-invoerveld niet gevonden");
}
const file = fileInput.files[0];
if (!file) {
throw new Error("Geen bestand geüpload");
}
// Lees bestand en bereid gegevens voor
const base64Data = await readFileAsBase64(file);
const resumesFiles = [
{
fileName: file.name,
data: base64Data,
},
];
const data = {
referenceId: "PROJECT-REFERENCE-ID", // Update met je project referentie ID
...formDataAsObject,
resumesFiles,
};
console.log("Gegevens verzenden:", data);
// Stuur gegevens naar API
const response = await fetch("https://functions.prod.jarvi.tech/v1/public-api/rest/v2/applicants", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
"X-Api-Key": JARVI_PUBLIC_API_KEY,
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`HTTP-fout! status: ${response.status}`);
}
const responseData = await response.json();
console.log("Gegevens succesvol verzonden:", responseData);
// Optioneel: Toon succesbericht aan gebruiker
alert("Sollicitatie succesvol ingediend!");
} catch (error) {
console.error("Fout:", error);
alert(`Fout: ${error.message}`);
}
});
```
# Projecten ophalen
Source: https://api-docs.jarvi.tech/nl/use-cases/projects
Deze API stelt je in staat om alle wervingsprojecten maar ook prospectieprojecten en alle bijbehorende informatie op te halen. Het doel is vaak om ze op je website weer te geven.
Hier is hoe je te werk gaat:
Als je nog geen API-sleutels hebt, volg dan de stappen in de sectie
Verkrijg mijn API-sleutels
Raadpleeg de gedetailleerde documentatie van de API GET projects. Het
zal waarschijnlijk ook nuttig zijn om het concept van Aangepaste velden te begrijpen
Je hebt de projecten opgehaald, misschien wil je ook sollicitaties naar Jarvi sturen:
Gebruiksscenario: sollicitaties naar Jarvi sturen