| 1 | --- |
| 2 | title: "POST /api/v7/sessions/{id}/transition — Advance State" |
| 3 | sidebarTitle: "POST /transition" |
| 4 | description: "Advance a session through the procurement state machine. Illegal moves return deterministic error codes. Every transition emits a timestamped audit event." |
| 5 | --- |
| 6 | |
| 7 | ProcureNet manages each procurement session as a strict state machine. Calling this endpoint moves the session forward to a target state you specify. The engine validates that the transition is permitted from the session's current state — if it isn't, you receive a deterministic `400` error with a machine-readable rejection code so your integration can handle it without guesswork. Every successful transition also emits a timestamped audit event, giving you a complete, structured record of how a session progressed. |
| 8 | |
| 9 | ## Authentication |
| 10 | |
| 11 | <Warning> |
| 12 | This endpoint requires a valid Bearer JWT in the `Authorization` header. Requests without a token, or with an expired or malformed token, receive `401 Unauthorized`. |
| 13 | </Warning> |
| 14 | |
| 15 | ``` |
| 16 | Authorization: Bearer <your_jwt> |
| 17 | ``` |
| 18 | |
| 19 | ## Path Parameters |
| 20 | |
| 21 | <ParamField path="id" type="string" required> |
| 22 | The session ID returned by `POST /api/v7/orchestrate`. Identifies the session whose state machine you are advancing. |
| 23 | </ParamField> |
| 24 | |
| 25 | ## Request Body |
| 26 | |
| 27 | <ParamField body="to" type="string" required> |
| 28 | The target state to transition the session into. The engine enforces which transitions are valid from the session's current state and rejects any that are out of sequence. |
| 29 | |
| 30 | Common target states include: |
| 31 | |
| 32 | | State | Meaning | |
| 33 | |---|---| |
| 34 | | `pending_payment` | Buyer has confirmed their cart; awaiting funding method | |
| 35 | | `escrow_pending` | High-value transaction detected; escrow acceptance required | |
| 36 | | `payment_processing` | Funding method submitted; payment is in flight | |
| 37 | | `completed` | Transaction finalized | |
| 38 | | `cancelled` | Session terminated by buyer or system | |
| 39 | </ParamField> |
| 40 | |
| 41 | ## Request Example |
| 42 | |
| 43 | ```typescript |
| 44 | const sessionId = 'sess_abc123'; |
| 45 | |
| 46 | const res = await fetch( |
| 47 | `https://api.procurenet.io/api/v7/sessions/${sessionId}/transition`, |
| 48 | { |
| 49 | method: 'POST', |
| 50 | headers: { |
| 51 | 'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...', |
| 52 | 'Content-Type': 'application/json' |
| 53 | }, |
| 54 | body: JSON.stringify({ to: 'pending_payment' }) |
| 55 | } |
| 56 | ); |
| 57 | |
| 58 | const result = await res.json(); |
| 59 | // { |
| 60 | // "session_id": "sess_abc123", |
| 61 | // "previous_state": "orchestrated", |
| 62 | // "current_state": "pending_payment", |
| 63 | // "transitioned_at": "2026-04-01T14:23:11Z" |
| 64 | // } |
| 65 | ``` |
| 66 | |
| 67 | ## Response Fields |
| 68 | |
| 69 | <ResponseField name="session_id" type="string" required> |
| 70 | Echoes back the session ID for correlation in your logs. |
| 71 | </ResponseField> |
| 72 | |
| 73 | <ResponseField name="previous_state" type="string" required> |
| 74 | The state the session was in immediately before this transition. |
| 75 | </ResponseField> |
| 76 | |
| 77 | <ResponseField name="current_state" type="string" required> |
| 78 | The state the session has moved into as a result of this call. This will match the `to` value you submitted. |
| 79 | </ResponseField> |
| 80 | |
| 81 | <ResponseField name="transitioned_at" type="string" required> |
| 82 | ISO 8601 timestamp of when the transition was recorded. This is the same timestamp written to the transition audit event. |
| 83 | </ResponseField> |
| 84 | |
| 85 | ## Response Example |
| 86 | |
| 87 | ```json |
| 88 | { |
| 89 | "session_id": "sess_abc123", |
| 90 | "previous_state": "orchestrated", |
| 91 | "current_state": "pending_payment", |
| 92 | "transitioned_at": "2026-04-01T14:23:11Z" |
| 93 | } |
| 94 | ``` |
| 95 | |
| 96 | ## Handling Illegal Transitions |
| 97 | |
| 98 | If you attempt a transition that is not valid from the session's current state, the engine returns `400` with a structured error body. Parse the `code` field to drive retry or fallback logic in your integration. |
| 99 | |
| 100 | ```typescript |
| 101 | if (!res.ok) { |
| 102 | const err = await res.json(); |
| 103 | // err.code === 'ILLEGAL_TRANSITION' |
| 104 | // err.current_state === 'payment_processing' |
| 105 | // err.attempted === 'pending_payment' |
| 106 | console.error(`Cannot transition: ${err.message}`); |
| 107 | } |
| 108 | ``` |
| 109 | |
| 110 | ```json |
| 111 | { |
| 112 | "code": "ILLEGAL_TRANSITION", |
| 113 | "message": "Cannot move to 'pending_payment' from 'payment_processing'.", |
| 114 | "current_state": "payment_processing", |
| 115 | "attempted": "pending_payment" |
| 116 | } |
| 117 | ``` |
| 118 | |
| 119 | <Info> |
| 120 | Every successful transition — and every rejected attempt — is emitted as a structured audit event. You can use these events to reconstruct the full lifecycle of any session for debugging or compliance purposes. |
| 121 | </Info> |
| 122 | |
| 123 | ## Error Codes |
| 124 | |
| 125 | | HTTP Status | Code | Description | |
| 126 | |---|---|---| |
| 127 | | `400 Bad Request` | `ILLEGAL_TRANSITION` | The `to` state is not reachable from the session's current state. | |
| 128 | | `400 Bad Request` | `UNKNOWN_STATE` | The `to` value is not a recognized session state. | |
| 129 | | `401 Unauthorized` | — | Missing, expired, or invalid Bearer JWT. | |
| 130 | | `404 Not Found` | — | No session exists for the provided `id`. | |
| 131 | | `410 Gone` | `SESSION_EXPIRED` | The session has passed its TTL and can no longer be transitioned. Start a new session with `POST /api/v7/orchestrate`. | |