curl --request POST \
--url https://api.example.com/v0/connectors/{connector_id}/api/{path} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json'import requests
url = "https://api.example.com/v0/connectors/{connector_id}/api/{path}"
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}
};
fetch('https://api.example.com/v0/connectors/{connector_id}/api/{path}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v0/connectors/{connector_id}/api/{path}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v0/connectors/{connector_id}/api/{path}"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v0/connectors/{connector_id}/api/{path}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v0/connectors/{connector_id}/api/{path}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
response = http.request(request)
puts response.read_body{
"receipts": [
{
"receipt_id": "<string>",
"state": "pending",
"diagnostics": [
{
"code": "<string>",
"message": "<string>",
"retryable": true,
"field": "<string>",
"resync_url": "<string>"
}
],
"source": {
"corpus_id": "<string>",
"namespace": "<string>",
"record_key": "<string>"
},
"processing": {
"state": "queued",
"phase": "materialization",
"message": "<string>"
},
"outcome": "created",
"record_id": "<string>",
"version_id": "<string>",
"availability": {
"state": "materialized",
"is_current": true,
"searchable": true
}
}
]
}{
"code": "<string>",
"message": "<string>",
"retryable": true,
"field": "<string>",
"resync_url": "<string>"
}{
"code": "<string>",
"message": "<string>",
"retryable": true,
"field": "<string>",
"resync_url": "<string>"
}Push data to a declared source route
Resolve a named POST route from the instance kind’s manifest (Plugin API 0.11; instance tokens and signatures since 0.12). For auth quivr_key, requires a Quivr bearer key with connector:push on the instance’s Corpus and Organization, checked before any plugin call. For auth instance_token, requires this instance’s bearer token; Quivr keys and other instances’ tokens are refused with 401 invalid_instance_token. Missing or invalid key is 401; missing action 403; out-of-scope, disabled or unknown instance and undeclared path 404; undeclared method 405 with Allow. JSON body is bounded to 1 MiB and the query to 8192 bytes. Malformed JSON is 400; a request_schema mismatch is 422. Up to 64 bounded headers are relayed without Authorization, Cookie or hop-by-hop headers. The plugin and ingestion stage is bounded to 9 seconds; audit persistence adds at most 2 seconds. Accepted items use normal ingestion idempotency and return 202 with receipts in item order, including withdrawals; the accepted plugin answer is replaced. An empty delivery returns an empty receipts array. A rejected item returns 422 item_rejected; other items may already be accepted, so retrying their stable revisions replays the same receipts. Transient failures return 503 with Retry-After. Engine-generated failures return the JSON Error envelope with the engine’s error code and no Quivr-Response-Origin header. A refused plugin verdict returns the plugin’s 4xx status, content type and body unchanged, marked with Quivr-Response-Origin: plugin. That header selects the plugin-defined response variant (x-quivr-plugin-response), even when its status overlaps an engine response; the engine response schemas below apply to responses without that header. Per-instance token buckets use the deployment default unless push_policy overrides it. Excess requests return 429 with Retry-After (integer seconds) before plugin invocation; callers outside push_policy.allowed_cidrs return 403 ip_not_allowed. Only explicitly trusted proxy networks may supply X-Forwarded-For addresses. Every attempt for an existing instance commits connector.push.received (2xx) or connector.push.refused and per-instance admin counters before the response is sent. Audit storage failure returns 503. Legacy routes without declared signature ingress keep their existing behavior. For auth signature, the plugin verifies the provider signature; the engine checks one nonempty signature header and optional signed Unix-seconds timestamp within the declared window (401 invalid_signature). PostgreSQL reserves signature and Idempotency-Key fingerprints per instance for window_seconds before the plugin call; duplicate keys return 409 push_replayed. Signature routes bypass the response cache: once replay reservations expire, the plugin verifies every new admitted request, including one reusing an old Idempotency-Key. Signature storage failures return JSON Error 503. GET challenges bypass POST signature guards. X declares receive at api/receive with a 300-second signature cache and no timestamp. Its legacy connector-webhooks address shares the same guard, push policy, audit counters and receipts until engine 1.0.0.
curl --request POST \
--url https://api.example.com/v0/connectors/{connector_id}/api/{path} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json'import requests
url = "https://api.example.com/v0/connectors/{connector_id}/api/{path}"
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}
};
fetch('https://api.example.com/v0/connectors/{connector_id}/api/{path}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v0/connectors/{connector_id}/api/{path}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v0/connectors/{connector_id}/api/{path}"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v0/connectors/{connector_id}/api/{path}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v0/connectors/{connector_id}/api/{path}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
response = http.request(request)
puts response.read_body{
"receipts": [
{
"receipt_id": "<string>",
"state": "pending",
"diagnostics": [
{
"code": "<string>",
"message": "<string>",
"retryable": true,
"field": "<string>",
"resync_url": "<string>"
}
],
"source": {
"corpus_id": "<string>",
"namespace": "<string>",
"record_key": "<string>"
},
"processing": {
"state": "queued",
"phase": "materialization",
"message": "<string>"
},
"outcome": "created",
"record_id": "<string>",
"version_id": "<string>",
"availability": {
"state": "materialized",
"is_current": true,
"searchable": true
}
}
]
}{
"code": "<string>",
"message": "<string>",
"retryable": true,
"field": "<string>",
"resync_url": "<string>"
}{
"code": "<string>",
"message": "<string>",
"retryable": true,
"field": "<string>",
"resync_url": "<string>"
}Authorizations
API key, not necessarily a JWT. Server derives Organization, permitted actions and Corpus scope; every resource access is authorized.
Headers
Optional opaque key of 1–256 bytes, scoped to the instance. Authorized requests replay the first completed plugin/ingestion answer within the deployment TTL for quivr_key and instance_token routes (default 24h), without calling the plugin or charging another rate token. Authentication, route/body validation, IP and rate refusals do not reserve keys. Reusing a key on another declared route still replays its original answer.
1 - 256Path Parameters
The declared relative route path, including any nested segments and filled template values.
8192Body
The body is of type any.
Response
Items accepted for ingestion; poll each Receipt through the normal API.
Show child attributes
Show child attributes