Platform / Platform
Marketplace partner integration
Any marketplace that rents out agents can provision Robots Center workspaces for its users and give the rented runtimes a way to report what they are doing. The integration is generic: partners are ordinary records with a slug and a scope list, and nothing in this plane is specific to one marketplace.
01 The shape of the integration
detailsThree principals, not one
A PARTNER is a platform-level machine principal that sits above workspaces and authenticates with an mpk_ key. A WORKSPACE belongs to one of the partner's users and holds their traces. An ENGAGEMENT is one job: one hiring, bound to one trace, for a bounded window. The runtime that actually does the work only ever holds an engagement credential, so it can never read or write outside the single job it was rented for.
Partner scopes
workspaces:provision (create and disconnect workspaces), engagements:manage (open, append to, mint tokens for and close engagements), webhooks:manage (subscribe to activity), sso:handoff (send a user into their workspace). A partner key is not a workspace key: it can never read a workspace's traces.
02 Provisioning a workspace
detailsPOST /api/v1/partner/workspaces
Idempotent find-or-link on your external_ref: the first call creates the user, workspace and bridge service agent and answers 201; later calls answer 200 with identical fields. A workspace disconnected earlier is reactivated by the same call. No long-lived credential is issued here at all — runtimes are credentialed per engagement, so there is nothing to lose or leak between jobs.
Why the login address looks synthetic
The Robots Center login identity is <partner-slug>+<external_user_id>@marketplace.invalid, never the human email you send. A partner asserts an address it has not proven, and letting that address claim the global email namespace would let any marketplace take over an existing account by naming it. The human email is stored on the identity account instead. The practical consequence: a provisioned workspace is reached through the SSO handoff below, not by signing in at robotscenter.net directly.
03 One engagement per job
detailsPOST /api/v1/partner/engagements
Idempotent per (partner, external_engagement_ref) — send your own job id and retries collapse onto the same engagement. The response carries engagement_id, trace_id, epoch, expires_at and events_url. Engagements default to 7 days and are clamped to 30.
POST /api/v1/partner/engagements/:id/tokens
Mints the only credential a rented runtime ever holds: append-only, scope engagement:append, default TTL 86400 seconds and always clamped to the engagement's own expiry. Hand it to the runtime together with events_url. It authenticates on that URL and nowhere else — presented to any other endpoint it fails signature verification outright.
POST /api/v1/partner/engagements/:id/close
Finalizes the trace with a terminal status and bumps the engagement epoch, which kills every outstanding runtime token for that job at its next request. Close when the job ends rather than waiting for the token to expire.
04 The runtime ingest route
examplePOST /api/v1/engagements/:engagement_id/events
The runtime's entire authority. Append-only: no read, no list, no finalize, and no trace id in the path — the target trace is read from the engagement row, so naming another job's trace is not a permission failure but an impossibility. Up to 50 events per request. Give every event a stable source_event_id: a retried batch is counted as a duplicate rather than appended twice, and the response reports accepted, duplicates and rejected.
Stop on 401 and 403
Both mean terminal: the token was revoked, the engagement was closed or expired, or the workspace is unavailable. Neither improves with retrying. Every other status is safe to retry. Authorization is re-checked against the engagement row on every single request, so revocation takes effect immediately rather than at token expiry.
curl -X POST https://robotscenter.net/api/v1/engagements/$ENGAGEMENT_ID/events \
-H "Authorization: Bearer $ENGAGEMENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"events":[{"source_event_id":"run-1:step-3","event_type":"tool.call","name":"read_file","status":"ok","duration_ms":12}]}'
# => {"accepted":1,"duplicates":0,"rejected":0}
05 What a runtime may record
detailsDeny-by-default event fields
A rented runtime writes into a workspace whose data belongs to somebody else, so nothing it sends is stored verbatim. Allowed: source_event_id, event_type, name, status, span_kind, duration_ms, started_at, metadata. Refused outright: input_payload, output_payload and error_payload — their names are recorded under metadata._dropped_payload_keys so an operator can see a runtime attempting to ship bodies.
Fields the server owns
sequence is assigned server-side, so a runtime cannot splice itself into an earlier position in the timeline. A supplied started_at is clamped into the trace's own window. metadata is capped at 32 keys, depth 3, 512 bytes per value and 4096 bytes total; name and every metadata value are truncated and denylist-redacted as untrusted provider text.
06 Receiving activity back
detailsPOST /api/v1/partner/webhook_subscriptions
Registers a subscription on the linked workspace and returns its signing secret exactly once. Defaults to trace.event.appended and trace.finalized. Deliveries carry event shape only — ids, event types, names, sequences, statuses and timings — never payload bodies, so a subscriber cannot become a back door around the ingest rules above.
Verifying a delivery
Concatenate the x-agentops-timestamp header and the raw body with a period, HMAC-SHA256 it with the subscription secret, hex-encode lowercase, and compare against x-agentops-signature in constant time. Verify against the raw body, not a re-encoded parse. Reject stale timestamps, and take the event type from the signed body rather than the header.
07 Sending a user into their workspace
detailsPOST /api/v1/partner/sso_handoffs
Mints a single-use handoff URL valid for 120 seconds, landing on a return_to path inside the app. Mint one per click and never store it: it is a live credential with a seconds-long life. Fetching the URL renders a CSRF-protected interstitial and never logs anyone in on GET; the POST behind it consumes the token, and a second visit is refused.
08 Disconnecting
detailsDELETE /api/v1/partner/workspaces/:external_ref
Revokes everything the partner holds: active engagements are revoked and their epochs bumped, the bridge service agent and any legacy credentials are revoked, partner webhook subscriptions are disabled, and the linked user's browser sessions are cut. The workspace, user, membership and traces are deliberately left intact — they are the user's own account and their own evidence. The link row is kept as disconnected so a later provision call reconnects to the same workspace.