Drop-offs API
Drop-offs are secure, schema-validated, point-to-point data handoffs between agents. They implement a custody-transfer model: a producing agent creates a drop-off with a JSON Schema, deposits a payload that matches the schema, and a designated consuming agent collects it. Payloads are encrypted with KMS at rest and automatically deleted after collection or expiry. Drop-off IDs are prefixed with drp_.
All endpoints require an Authorization: Bearer sk_live_... header with a valid operator API key or team member API key.
Drop-off Lifecycle
Every drop-off follows a strict state machine:
- created — Drop-off location established with schema, producer, and consumer agents. Waiting for data deposit.
- deposited — Payload has been deposited and validated against the schema. Waiting for consumer to collect.
- collected — Consumer has collected the payload. Encrypted data is wiped from storage.
- expired — TTL elapsed before collection. Data is wiped by the background worker.
- failed — Terminal failure state.
Transitions are one-way. There is no way to revert a state or reuse a drop-off after collection or expiry.
Create Drop-off
POST /v1/dropoffs
Create a new drop-off location with a JSON Schema, producing agent, and consuming agent. The schema is used to validate the payload at deposit time — any data that does not conform will be rejected.
Request Body
{
"from_agent": "agt_7kx9m2nq4p",
"to_agent": "agt_3fh8j1kw6r",
"schema": {
"type": "object",
"properties": {
"invoice_id": { "type": "string" },
"amount": { "type": "number", "minimum": 0 },
"currency": { "type": "string", "enum": ["USD", "EUR", "SEK"] }
},
"required": ["invoice_id", "amount", "currency"]
},
"ttl_seconds": 1800,
"on_expire": "notify"
}- from_agent (string, required) — Agent ID of the producer who will deposit data.
- to_agent (string, required) — Agent ID of the consumer who is authorized to collect.
- schema (object, required) — JSON Schema defining the expected payload shape. Validated with Ajv.
- ttl_seconds (number, optional) — Time-to-live in seconds. Min: 60, max: 86400 (24 hours). Default: 1800 (30 minutes).
- on_expire (string, optional) — Action when TTL expires: "notify", "retry", or "fail". Default: "notify".
Request Example
curl -X POST https://api.getstack.run/v1/dropoffs \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_live_op_abc123" \
-d '{
"from_agent": "agt_7kx9m2nq4p",
"to_agent": "agt_3fh8j1kw6r",
"schema": {
"type": "object",
"properties": {
"invoice_id": { "type": "string" },
"amount": { "type": "number" }
},
"required": ["invoice_id", "amount"]
},
"ttl_seconds": 1800
}'Response — 201 Created
{
"id": "drp_9xm3kR7wL2",
"operator_id": "op_abc123",
"from_agent_id": "agt_7kx9m2nq4p",
"to_agent_id": "agt_3fh8j1kw6r",
"status": "created",
"schema": {
"type": "object",
"properties": {
"invoice_id": { "type": "string" },
"amount": { "type": "number" }
},
"required": ["invoice_id", "amount"]
},
"on_expire": "notify",
"payload_hash": null,
"created_at": "2026-04-15T10:30:00.000Z",
"deposited_at": null,
"collected_at": null,
"expires_at": "2026-04-15T11:00:00.000Z"
}Error Responses
- 400 Bad Request — Invalid JSON Schema, ttl_seconds out of range, or missing required fields.
- 401 Unauthorized — Missing or invalid Authorization header.
- 404 Not Found — from_agent or to_agent does not exist.
- 429 Too Many Requests — Drop-off creation rate limit exceeded for your tier.
Deposit Payload
POST /v1/dropoffs/:id/deposit
Deposit a payload into a created drop-off. The payload is validated against the drop-off's JSON Schema using Ajv. If validation fails, the deposit is rejected with detailed errors. The payload is encrypted with KMS before storage.
Request Body
{
"agent_id": "agt_7kx9m2nq4p",
"payload": {
"invoice_id": "INV-2026-0042",
"amount": 1250.00,
"currency": "USD"
}
}- agent_id (string, required) — The agent performing the deposit. Must match the from_agent on the drop-off.
- payload (object, required) — The data to deposit. Must conform to the drop-off schema.
Request Example
curl -X POST https://api.getstack.run/v1/dropoffs/drp_9xm3kR7wL2/deposit \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_live_op_abc123" \
-d '{
"agent_id": "agt_7kx9m2nq4p",
"payload": {
"invoice_id": "INV-2026-0042",
"amount": 1250.00,
"currency": "USD"
}
}'Error Responses
- 400 Bad Request — Payload does not match schema, or drop-off is not in "created" status.
- 401 Unauthorized — Missing or invalid Authorization header.
- 403 Forbidden — agent_id does not match the from_agent on the drop-off.
- 404 Not Found — Drop-off does not exist.
Schema validation is a hard gate. There is no way to bypass it or deposit data that does not conform to the declared schema. This ensures data integrity across the producer-consumer boundary.
Collect Payload
POST /v1/dropoffs/:id/collect
Collect the payload from a deposited drop-off. Only the designated consumer agent (the to_agent) can collect. After successful collection, the encrypted payload is permanently wiped from storage.
Request Body
{
"agent_id": "agt_3fh8j1kw6r"
}- agent_id (string, required) — The agent performing the collection. Must match the to_agent on the drop-off.
Request Example
curl -X POST https://api.getstack.run/v1/dropoffs/drp_9xm3kR7wL2/collect \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_live_op_abc123" \
-d '{
"agent_id": "agt_3fh8j1kw6r"
}'Response — 200 OK
{
"payload": {
"invoice_id": "INV-2026-0042",
"amount": 1250.00,
"currency": "USD"
}
}Error Responses
- 401 Unauthorized — Missing or invalid Authorization header.
- 403 Forbidden — agent_id does not match the to_agent on the drop-off.
- 404 Not Found — Drop-off does not exist.
- 409 Conflict — Payload has already been collected.
- 410 Gone — Drop-off has expired and data has been wiped.
Collection is a one-time operation. Once data is collected, it is permanently deleted from STACK's storage. The consumer receives the payload in the response and is responsible for handling it from that point forward.
Get Drop-off Status
GET /v1/dropoffs/:id
Retrieve the current status and metadata for a drop-off. This never returns the actual payload — only the schema, status, timestamps, and related IDs.
Request Example
curl https://api.getstack.run/v1/dropoffs/drp_9xm3kR7wL2 \
-H "Authorization: Bearer sk_live_op_abc123"List Drop-offs
GET /v1/dropoffs
List all drop-offs for your operator account.
Request Example
curl https://api.getstack.run/v1/dropoffs \
-H "Authorization: Bearer sk_live_op_abc123"Manually Expire Drop-off
POST /v1/dropoffs/:id/expire
Manually expire a drop-off before its TTL elapses. This transitions the drop-off to the expired state and wipes any deposited payload data. Useful for cancelling a drop-off that is no longer needed.
Request Example
curl -X POST https://api.getstack.run/v1/dropoffs/drp_9xm3kR7wL2/expire \
-H "Authorization: Bearer sk_live_op_abc123"TTL and Automatic Expiry
Drop-offs are ephemeral by design. A background worker process monitors active drop-offs and transitions expired ones to the expired state. When a drop-off expires:
- Status transitions to "expired" regardless of current state (created or deposited).
- Encrypted payload is permanently deleted from storage.
- The drop-off metadata (schema, timestamps, IDs) is preserved for audit purposes.
- The on_expire action is triggered (notify, retry, or fail).
Tier Limits
Drop-off usage is metered monthly:
- Free — 100 drop-offs/month
- Developer ($9.99/mo) — 5,000 drop-offs/month
- Studio ($99/mo) — 50,000 drop-offs/month
- Enterprise — Unlimited